Layer 5 · Next.js

Place your bet: what does view-source show for a plain React app?

Empty div, one script. Everything the user sees is built by JavaScript after the page loads. Google will run that JavaScript, but later, on a slower second pass. Everything else that reads your HTML, like link previews in Slack and iMessage, most other bots, anything that just fetches the source, sees an empty page. A slow phone sees a white screen while 300 kB downloads. And there's no server in the picture at all to fetch data before the first paint.

Next.js exists to fix exactly this, and it does so by adding a server.

bet first
view-source: plain React (Vite)
<!doctype html>
<html><body>
  <div id="root"></div>
  <script src="/assets/index-a3f9.js"></script>
</body></html>
What Next.js is

React, plus a server, plus three opinions.

React draws. Next.js decides everything around the drawing. Specifically, three things.

1 · Which file answers which URL

Folders in app/ are routes. No router config, no route table. The filesystem is the map.

2 · Where the HTML gets made

On the server per request, at build time once, or in the browser. Per page, your call.

3 · Where each component runs

On the server only (can read the DB, ships no JS), or on the server and then again in the tab (can use state and clicks). The "use client" line is the border.

One slide each. Then the round trip to the database.

Decision 1

The folder is the URL.

Type a URL, or pick one. The file that answers it lights up. [slug] in a folder name means "anything goes here"; the page receives it in params, which since Next 15 is a promise you await. Every layout.tsx on the way down wraps the page.

app/
app/layout.tsx ← wraps everything
app/page.tsx → /
app/about/page.tsx → /about
app/blog/layout.tsx ← wraps /blog/*
app/blog/page.tsx → /blog
app/blog/[slug]/page.tsx → /blog/:slug
app/dashboard/layout.tsx ← wraps /dashboard/*
app/dashboard/settings/page.tsx
app/api/posts/route.ts → /api/posts (JSON, no page)
app/not-found.tsx → anything else
Decision 2

Where the HTML gets made changes what the user sees, and when.

CSR: browser gets an empty page, downloads JS, renders. SSR: server renders HTML per request; browser shows it, then downloads JS and "hydrates" (attaches the click handlers). SSG: like SSR, but the HTML was made once at build time and cached.

Bet: on a slow phone, which mode shows content first?

SSG, then SSR close behind. Both send real HTML in the first response. CSR can't show anything until the JS arrives. But notice: in every mode the buttons only work after hydration. Fast-to-see and fast-to-click are different clocks.

Those are the older names. In the App Router the build output calls them static (○, made once at build) and dynamic (ƒ, made per request); CSR is what a "use client" component does after hydration.

bet, then run each mode
one request, three modes
0 ms3000 ms
the user's screen
first content: interactive:
Decision 3

Every component runs on the server, unless it says "use client".

Server components run in Node, once, and send finished HTML. They can read the database directly and ship zero JavaScript to the browser. But they can't hold state or handle clicks, because they aren't there when the click happens.

Client components are the React you know, but the name misleads: they run on the server too. Next.js renders them to HTML on the server like everything else, then also ships their JavaScript so they can hydrate and take clicks. "use client" means "also send this to the browser", not "only run this in the browser". Everything a client file imports crosses the border with it.

Click a component to toggle the directive. Then try the two errors you've definitely seen.

the boundary
Pageserver
PostListserver
Sidebarserver
Postserver
LikeButtonserver
Server-side: 5 of 5. Bundle shipped to the browser: 0 kB of your components.
The word you keep seeing

Hydration: the HTML is already there. React catches up to it.

The server sent finished HTML, so the page is visible. But it's a photograph: nothing responds. Then the JavaScript for your client components arrives, React renders them in memory, matches the result to the HTML that's already on screen, and attaches the event handlers. Now it's alive.

If what React renders doesn't match what the server sent (a timestamp, window.innerWidth, a random id), you get the "hydration mismatch" warning. It's not a bug in React; it's the photograph and the live view disagreeing.

in order
  1. Server runs server components → HTML, plus a list of which client components go where.
  2. Browser paints the HTML. Visible. Not interactive.
  3. Browser downloads only the JS for client components.
  4. React renders those in memory, compares to the HTML, attaches handlers. Interactive.

Server components never reach step 3. That's the whole reason they ship no JS.

Down to the database

The server can render a page. Where does the content come from?

Server components can call the database directly. Client components have to ask the server. Forms have to get their data somewhere. This is the round trip.