Server Components
app/dashboard/page.tsx — a Server Component (no directive needed)
// No 'use client' at the top — this file is a Server Component
// simply because that is the default.
async function getStats() {
const res = await fetch('https://api.example.com/stats')
return res.json()
}
export default async function DashboardPage() {
const stats = await getStats()
return (
<section>
<h1>Dashboard</h1>
<p>Total users: {stats.userCount}</p>
</section>
)
}What "renders on the server" buys you
A Server Component executes entirely on the server (at build time or request time, depending on the route). It produces HTML, and that HTML — not the component's source code, not a JavaScript bundle for it — is what gets sent down to the browser. This has a few consequences that are each worth calling out on their own.
No JavaScript for that component ships to the client. The component's code never has to be downloaded, parsed, or executed in the browser at all — only its rendered output does.
Direct backend access, with no API layer. A Server Component can query a database, read a file from disk, or use a secret API key directly in its body, because that code never leaves the server. There is no need to build a
/api/...route just to hand data to the page that needed it.Server-only dependencies stay server-only. Large libraries used only for data-fetching or parsing (an ORM, a Markdown-to-HTML renderer, etc.) never inflate the client bundle, because their code runs where the component runs — the server.
Direct backend access — no API route required
app/products/page.tsx
import { db } from '@/lib/db'
// db.product.findMany() talks straight to the database.
// This code runs on the server, so the database credentials,
// connection string, and query logic never reach the browser.
export default async function ProductsPage() {
const products = await db.product.findMany()
return (
<ul>
{products.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
)
}What Server Components can't do
The trade-off for all of this is that Server Components have no presence in the browser at runtime — they finish their job (producing HTML) before the page ever reaches the user's machine. That rules out anything that depends on the browser being present while the component is "alive".
useState or useReducer, cannot use lifecycle/effect hooks like useEffect, cannot attach event handlers such as onClick or onChange, and cannot touch browser-only APIs like window, localStorage, or document. All of these require the component to actually be running in the browser, which a Server Component, by definition, never is.Every component in the App Router is a Server Component by default — no directive is required.
Server Components render on the server; only their resulting HTML (plus minimal hydration instructions for any client children) is sent to the browser.
They ship zero JavaScript to the client for their own code, which is a major bundle-size win.
They can access databases, the filesystem, and secrets directly — no API route needed.
They cannot use state, effects, event handlers, or browser-only APIs — that requires a Client Component.