Choosing a rendering strategy per route
Decide each route by who reads its first HTML, whether everyone gets the same page, and how fresh it must be.
Predict first
Before you read: predict
The proctor console shows the alerts and candidate tiles of the room a proctor signs in to. Proctors keep it open for the whole exam and acknowledge and filter alerts all the time, while new alerts stream in over a live feed.
How should the console render?
The 30-second version
Choose per route, not per app. Content a crawler must read belongs in the first HTML. A page that is the same for everyone is prerendered and revalidated when it changes. Personal or second-by-second data never goes in a shared page: stream it into a hole, or let the browser fetch it. Server rendering costs work on every request.
Decide each route by who reads its first HTML, whether everyone gets the same page, and how fresh it must be.
How it works
Step 1 of 12
Analogy: A public aquarium
Each tank carries a printed panel, the same for every visitor and readable by anyone walking past, including the guidebook writer who copies only what is on the walls. A panel is reprinted when the tank's residents change. A keeper gives each school group its own talk, so at the busy hour groups queue for keepers. The handheld audio guide says nothing until a visitor starts it. The shark tank's feeding screen shows the live camera, not a panel. Some panels have a clip where the keeper slides in the group's own feeding-time card.
| At the aquarium | In rendering |
|---|---|
| the printed tank panel | a prerendered page, shared by every visitor (step 3) |
| the guidebook writer reading the walls | a crawler reading the first HTML (step 2) |
| reprinting a panel when the residents change | revalidating when content changes (step 4) |
| a keeper's talk for each group, queues at the busy hour | server rendering per request, its cost at a peak (step 7) |
| the audio guide, silent until started | client rendering: a placeholder in the first HTML (step 2) |
| the live feeding screen | second-by-second data (step 4) |
| a panel with a clip for the group's own card | a shared shell with a per-request hole (step 6) |
| a group's name never painted on a panel | personal data never in a shared response (step 5) |
In real terms: each display answers who reads it, whether it is shared, and how fresh it must be.
Breaks down when: a reprint costs paint, while a revalidated page regenerates in the background, and a web route can have far more pages than an aquarium has tanks (step 11). Wrong inference: "every page must be printed in advance."
Contrasting case: a museum with only an audio tour and bare walls: a visitor who never starts the device, like a crawler, learns nothing.
Examples, run for real
4.1 ExamHall: the console that was server-rendered
EXH-1480: to make the proctor console "faster", it was server-rendered on every open. The fixture builds the console both ways; the replies are for proctor Ines in Hall B.
console server per request first HTML: alert text x2 · room name yes · console code in client JS yes
console in the browser first HTML: alert text x0 · room name no · console code in client JS yes
The server-rendered reply carries each alert twice, once as markup and once in the data the browser needs to hydrate it, and it is built per proctor on every open. The browser still downloads the same console code (step 10). The live feed replaces the snapshot within a second. MR !1680 went back to a client-rendered console behind a small shell. Nobody crawls a signed-in console, so its empty first HTML costs nothing (step 2).
4.2 Proof domain, e-commerce catalog: one product page, three ways
A boot's product page is built three ways; the hole holds the "Hi Ana, 1 item in your basket" greeting. Shoppers Ana and Ben each load it.
product prerendered shell holds product yes · both replies start with the stored shell yes · Ana's name in Ana's reply no, in Ben's no, in the stored prerender no · cache-control s-maxage=31536000
product per request shell holds product no · both replies start with the stored shell yes · Ana's name in Ana's reply yes, in Ben's no, in the stored prerender no · cache-control private, no-cache, no-store, max-age=0, must-revalidate
product shell + one hole shell holds product yes · both replies start with the stored shell yes · Ana's name in Ana's reply yes, in Ben's no, in the stored prerender no · cache-control private, no-cache, no-store, max-age=0, must-revalidate
The prerendered page is one copy, stored at build and marked for shared caches, so it can greet nobody (step 5). Rendered per request, the greeting works, but the stored part holds only a fallback, and the product is rendered again on every visit (step 7). The shell with one hole keeps the product in the stored copy everyone gets and renders only the greeting per request (step 6). How Next builds the shell belongs to next-rendering-modes-ppr.
Try it
Answer first, then check. Nothing is saved.
Question 1 of 6
The shop has 400,000 product pages. 2% of them get 80% of the visits, and half of the rest are opened less than once a month. Every page is the same for every shopper, and most visits come from phones.
How should product pages render?
Question 2 of 6
Category pages such as the boots page are rendered on the server for every request. Every shopper sees the same page, its content changes about five times a day, and its product grid is the page's LCP element. At the evening peak the servers fall behind and TTFB climbs.
Which change should the team make?
Question 3 of 6
A product page is a Server Component that prints the product's name, price and description, then <ReviewsSlot />. ReviewsSlot is a client component that returns <Reviews />, and const Reviews = dynamic(() => import('./Reviews'), { ssr: false, loading: () => <p>Loading reviews…</p> }). A search crawler that runs no JavaScript fetches the page.
What does its HTML response hold?
Question 4 of 6
A boot's product page is prerendered once and shared by every shopper. It shows a description that search results quote; a price that is the same for everyone and changes a few times a day; a large main photo, which is the page's LCP element; and a "Check a store" link that shows the stock at the store the shopper then picks. The team will take one part out of the HTML and let the browser fetch it after load.
Which part?
Question 5 of 6
The shop's category page for walking boots is a client component that fetches its product list in an effect after it mounts. An SEO audit that reads only the HTML response finds none of the product names.
Which change puts them into that response?
Question 6 of 6
The shop's rendering plan has four lines. The home page is prerendered and revalidated when marketing edits it. Product pages are a prerendered shell, with each shopper's basket count streamed into it per request. The basket page is prerendered and revalidated every hour. The order-tracking page renders in the browser and polls the courier's position.
Which line is the bug?
Go deeper
Pro tips
- Fetch the route the way its readers do before choosing. Request it with no cookies and no JavaScript (what a crawler gets), then as two different signed-in users, and compare the replies. It shows what the first HTML holds and whether one reply is shared. When: a route's rendering is being chosen or changed (steps 2, 5). Verify.
- Say the route's readers and freshness out loud in a design round. Naming who reads the first HTML and how stale each part may be turns "SSR or not" into a per-route answer. When: the architecture minutes of a frontend round (step 1). Status: DOC (web.dev, Rendering on the Web: "you can always choose to use server-side rendering for some pages and not others"); not run.
Source
Next.js pages were fetched as the Markdown nextjs.org serves on Accept: text/markdown (docs version 16.3.7, the current docs; the fixture runs 16.3.6), with each Markdown link reduced to its text.
- Addy Osmani and Jason Miller, Rendering on the Web (web.dev, last updated 2026-01-05): https://web.dev/articles/rendering-on-the-web ·
webdev-rendering-on-the-web.txt. The per-page choice (Netflix), SSR's compute cost and TTFB, "doesn't necessarily give you less work", personalisation and static rendering, static needing every URL, SEO and client rendering. Supports steps 1–3, 5, 7, 10, 11. Named in the curriculum row. Valid as of 2026-09-30. - web.dev, Time to First Byte (TTFB): https://web.dev/articles/ttfb ·
webdev-ttfb.txt. TTFB as a rough guide; a server-rendered page with a higher TTFB but better FCP and LCP. Supports step 8. Valid as of 2026-09-30. - React, Server Components: https://react.dev/reference/rsc/server-components ·
react-server-components.txt. Components run at build or per request; content visible on first load without the rendering libraries. Background for steps 2–3 (no item keyed). Named in the curriculum row. Valid as of 2026-09-30. - Next.js docs, Caching (Cache Components): https://nextjs.org/docs/app/getting-started/caching ·
next-caching.txt. Named in the curriculum row; supports step 6 ("doesn't opt-in the whole route into dynamic rendering"). Valid as of 2026-09-30. - Next.js docs, Building public pages: https://nextjs.org/docs/app/guides/public-static-pages ·
next-public-static-pages.txt. Shared pages prerendered and reused, "lower server costs"; shared catalog data cached; request-specific inputs streamed. Supports steps 3, 6. - Next.js docs, ISR guide: https://nextjs.org/docs/app/guides/incremental-static-regeneration ·
next-isr.txt. Updating static content without a rebuild, large numbers of pages, on-demand revalidation, "1 hour instead of 1 second". Supports steps 4, 11. - Next.js docs, Streaming: https://nextjs.org/docs/app/guides/streaming ·
next-streaming.txt. TTFB equals the slowest query without streaming; an LCP element inside a Suspense boundary waits for its swap. Supports steps 7, 9. - Next.js docs, Single-page applications and Client-side data fetching: https://nextjs.org/docs/app/guides/single-page-applications · https://nextjs.org/docs/app/guides/client-side-data-fetching ·
next-spa.txt,next-client-data-fetching.txt. Client components prerender at build;next/dynamicfor browser-only components; polling libraries; a browser fetch for data not needed until an interaction. Supports steps 2, 4, 12. - Also saved, not quoted:
next-rendering-philosophy.txt,next-auth-cache-components.txt,next-server-client-components.txt.
Related lessons
Checked against the tools and docs as of 2026-09-30.