Skip to content
C.W.K.
Stream
Lesson 01 of 08 · published

Server Components Are the Default

~22 min · RSC, default, server

Level 0Curious
0 XP0/68 lessons0/11 achievements
0/120 XP to next level120 XP to go0% complete

The new default

In the App Router, every component is a Server Component unless you opt out. This is the inverse of older React: instead of "everything in the browser unless you SSR it," you have "everything on the server unless you mark it as Client."

What that means in practice

  • You can write async components — they're awaited during render.
  • You can read databases, files, and secrets directly — nothing leaks to the browser.
  • Heavy libraries (markdown parsers, syntax highlighters, date formatters) ship zero JavaScript for the rendered output.
  • The HTML you ship is the rendered output, not a hydration shell.

What Server Components can't do

No state, no effects, no event handlers, no browser APIs. The moment you need any of those, the component becomes a Client Component (next lesson).

Start every App Router component on the server and promote only the smallest node that requires state, an event, a ref, or a browser API. Mark database and secret-bearing modules with server-only so the import direction is enforced by the build instead of team memory. Server-only execution does not make every rendered value safe. A secret can still leak if you place it in JSX or serialize it into a client prop. Protect both the code location and the output data; they are different boundaries.

Build a page with a direct database read and a tiny click counter. Verify the database module is absent from client chunks, the server content remains with JavaScript disabled, and only the counter loses interaction.

Code

Server Component, no directive needed·tsx
// app/users/page.tsx — runs on the server
import { db } from '@/lib/db';

export default async function UsersPage() {
  const users = await db.user.findMany();        // direct query, no API
  const apiKey = process.env.SECRET_API_KEY;     // server-only env
  return (
    <ul>
      {users.map(u => <li key={u.id}>{u.name}</li>)}
    </ul>
  );
}
// HTML ships; this component contributes 0 bytes of JS
Marker: server-only modules·ts
// lib/db.ts
import 'server-only';
import { PrismaClient } from '@prisma/client';

export const db = new PrismaClient();
// Importing this from a Client Component is a build-time error.

External links

Exercise

Take a list-view page in your app, replace any client-side fetch with a Server Component async render, and confirm the network tab shows zero JavaScript for the list itself.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.