Skip to content

Rendering modes: static, dynamic, ISR, streaming, PPR

The build stores everything that does not need the request; only the parts that do stream in, and a stored result changes when revalidated.

Predict first

Before you read: predict

The proctor console's room page runs on Next 16 with Cache Components on. It prints the room name, which comes from a function marked 'use cache'. Below it sits <Suspense fallback={<p>Loading rules…</p>}><Rules /></Suspense>, and Rules gets the hall rules with a plain fetch to the exam service. The rules are the same for every proctor and change once a term.

After next build, what does the HTML stored for this route hold?

Pick one

The 30-second version

Next renders each route at build and stores what finishes. A part that reads the request (cookies, headers, search params, connection()) or does uncached I/O cannot finish there. With Cache Components only that part waits: the shell keeps fixed and 'use cache' parts plus its fallback, and the hole streams in per request. Revalidation marks a stored result stale.

The build stores everything that does not need the request; only the parts that do stream in, and a stored result changes when revalidated.

How it works

Step 1 of 11

At build, Next renders every route. What completes is stored as HTML plus an RSC payload and served to every visitor.
In the kitchen
  • bags packed before opening → the build's stored output (step 1)

Analogy: A scout camp's kit store

Before camp opens, the storekeeper packs kit bags, the same for every scout, and stacks them. Each bag has an empty name-tag pocket that she fills at the hatch for the scout in front of her. Under the old store rule, any bag needing a name tag was packed from scratch at the hatch. Anything she must check with the ranger at the moment of issue waits for that moment, unless it went into the morning packing list. When the leader marks the list changed, the next scout still gets a bag from the pile, and that issue sends one to be repacked. At the hatch the bag comes first; the torch and the map follow as each is fetched, whichever is ready first.

At the kit store In Next
bags packed before opening the build's stored output (step 1)
the empty name-tag pocket, filled at the hatch a hole with its fallback in the shell (step 4)
the old rule: a name tag means packing from scratch the previous model's dynamic route (step 3)
checking with the ranger at the moment of issue uncached I/O (step 2)
the morning packing list 'use cache' (step 6)
"list changed": the next scout still gets an old bag stale served, regeneration starts (step 10)
torch and map handed over as each is ready holes streamed in arrival order (step 8)

In real terms: the build keeps whatever needs no request; request reads become holes, and stored results change only when revalidated.

Breaks down when: a storekeeper would refuse to hand out a bag with an empty pocket; Next stops the build instead when a request read has no fallback (step 5). Wrong inference: "a missing boundary just renders slowly."

Contrasting case: a store that packs nothing ahead and makes every bag at the hatch: that is the previous model's dynamic route, or a server that renders every request.

Examples, run for real

4.1 ExamHall: one badge made the room page dynamic

EXH-1510: the proctor console's room page has a fixed header and rules, plus a sign-in badge that reads the proctor cookie, with no <Suspense> above it. The same page was built twice; under the previous model proctors Ines and Omar each open it.

4.1 Cache Components off /room dynamic · stored HTML: none · each reply rendered with header and rules yes
4.1 Cache Components off replies: Ines sees Ines Duarte · Omar sees Omar Haddad · badge renders logged by the server 2 (2 requests)
4.1 Cache Components on (MR !1710) next build exit 1 · uncached or runtime data outside <Suspense> · names /room yes

Without Cache Components, one cookies() call left nothing stored: every request rendered the header and rules again (step 3). MR !1710 turned Cache Components on, and the same page no longer built: next build stopped on /room, naming the request read outside a boundary (step 5). With the flag on, next build prerenders every page.

4.2 Proof domain, e-commerce catalog: one streamed hole

A rain jacket's page streams its stock count. The script holds the stock read, looks at what has arrived, then releases it.

4.2 stock read held: received heading yes · "Checking stock…" yes · "In stock: 7" no
4.2 stock read released: "In stock: 7" received yes · status 200

The shell reached the browser while the data was still out, and the stock arrived once its read was released (step 8).

4.3 Proof domain, e-commerce catalog: a stored list and revalidation

The walking-boots category lists its boots from a 'use cache' function. A third boot is added to the product service.

4.3 before the edit: 2 boots · after the edit, no call: 2 boots, then 2 boots · list reads by the running app so far 0
4.3 handler runs revalidatePath('/c/boots') (status 200) · next request: 3 boots (x-nextjs-cache MISS) · list reads 1

Nothing re-reads a stored result by itself: two visits after the edit still show two boots (step 9). A route handler's revalidatePath made the next request read the list again and show three (step 11).

Try it

Answer first, then check. Nothing is saved.

Question 1 of 5

The boots category page should be stored whole at build, but next build marks it ◐ (Partial Prerender). Cache Components is on. The page and its four parts:

Which part puts the request-time hole in the page?

Code for this question
export default function Page({ searchParams }) {  return <main>    <ShopHeader />    <Suspense fallback={<p>Loading products…</p>}><Products /></Suspense>    <CurrencyNote />    <Suspense fallback={<p>Sorting…</p>}><SortLabel searchParams={searchParams} /></Suspense>  </main>;}function ShopHeader() { return <h1>{process.env.SHOP_NAME ?? 'Fellside Outdoor'} · Walking boots</h1>; }async function getProducts() { 'use cache'; return read('v', 'products'); }async function Products() { return <p>Products: {await getProducts()}</p>; }async function SortLabel({ searchParams }) { const { sort } = await searchParams; return <p>Sorted by {sort ?? 'popularity'}</p>; }// CurrencyNote.jsx starts with 'use client':function CurrencyNote() {  const [lang, setLang] = useState(null);  useEffect(() => { setLang(navigator.language); }, []);  return <p>Prices shown for {lang ?? 'your region'}</p>;}
Pick one

Question 2 of 5

A boot's product page prints its price from a 'use cache' function with cacheLife('days') and cacheTag('price-boot'), and the page was stored at build. The price changes from €120 to €95 in the product service, whose webhook then calls a route handler that runs revalidateTag('price-boot', 'max'). Ana is the next visitor.

What does she get?

Pick one

Question 3 of 5

Hall C's feed page (Cache Components on) renders a heading, then <Suspense fallback={<p>Loading alerts…</p>}><Alerts /></Suspense>, then <Suspense fallback={<p>Loading roster…</p>}><Roster /></Suspense>. Alerts and Roster each read the exam service with a plain fetch. A proctor opens the page. The roster's read has returned; the alerts' read has not.

What has the proctor's browser received from the server for this page at that moment?

Pick one

Question 4 of 5

With Cache Components on, the Kestrel 2 tent's page renders <main><h1>Kestrel 2 tent</h1><TentSpecs /><RecentlyViewed /></main>. TentSpecs prints three fixed lines. RecentlyViewed reads the shopper's recent cookie with cookies() and lists the products it names. next build stops on this route with an error. The team wants the heading and specs in the HTML the build stores, and each shopper's own list on the page.

Which change does that?

Pick one

Question 5 of 5

The Loch 40 rucksack costs €89 on its product page /rucksack and on the deals page /deals; both print it from one 'use cache' function tagged price-loch40. Editors change prices in an admin tool outside the Next app. After a save to €74, the tool calls a route handler in the Next app, and the editor reloads /deals straight away to check the price.

Which call should the handler make?

Pick one

Go deeper

Pro tips
  1. Read the route table after every build that adds a data read. The ○ / ◐ / ƒ marker beside each route says whether the build stored it whole, as a shell with holes, or not at all; a route that jumps from ○ to ƒ has a request read that a boundary did not contain. When: reviewing a pull request that touches data fetching (steps 3, 4). Verify (RAN, Node 24.19.0, build.mjs, which saves Next's route table for every fixture app in builds/).
  2. When a blocking-route error appears only in CI, rebuild with next build --debug-prerender. It prints prerender stack traces that name the component. When: the error names a route but not the component (step 5). Status: DOC (Next.js docs, blocking-route message: "run next build --debug-prerender for prerender stack traces that name the exact component"); not run.
Source
  • Next.js docs, Caching (Cache Components): https://nextjs.org/docs/app/getting-started/caching · next-caching.txt. Named in the curriculum row. Runtime APIs (cookies, headers, searchParams), uncached data behind <Suspense>, 'use cache' joining the static shell, "Reading cookies() here doesn't opt-in the whole route into dynamic rendering, the way the previous rendering model did", PPR as "the default behavior with Cache Components", connection() for per-request values. Supports steps 1–4, 6, 7. Valid as of 2026-09-30.
  • Next.js blog, Next.js 16 and Next.js 16.3: https://nextjs.org/blog/next-16 · https://nextjs.org/blog/next-16-3 · next-16-blog.txt, next-16-3-blog.txt. Named in the curriculum row. Cache Components and 'use cache' in 16; the 16.3 release notes. Background for steps 4, 6.
  • Next.js docs, cacheComponents, use cache, cacheLife: next-cache-components-config.txt, next-use-cache.txt, next-cache-life.txt. The flag, the directive, lifetimes (the seconds profile's expire is 1 minute), and "Cached functions and components cannot access runtime APIs like cookies(), headers(), or searchParams".
  • Next.js docs, blocking-route message: https://nextjs.org/docs/messages/blocking-route · next-blocking-route.txt. "Next.js interprets your intent as expecting the data to load on every user request" for fetch and database I/O; the fixes. Supports steps 2, 5 and pro tip 2.
  • Next.js docs, revalidateTag: https://nextjs.org/docs/app/api-reference/functions/revalidateTag · next-revalidate-tag.txt. "The next request for that data kicks off a revalidation and is served stale content while it runs"; { expire: 0 } makes "the next request … a blocking revalidate"; "A revalidation is triggered by a request, not by the revalidateTag call"; for another profile "only its expire is read".
  • Next.js docs, revalidatePath and updateTag: next-revalidate-path.txt, next-update-tag.txt. In route handlers revalidatePath "Marks the path for revalidation. The revalidation is done on the next visit", and "only the specified path gets fresh data on the next visit. Other pages that use the same data tags will continue to serve cached data"; updateTag in Server Actions only.
  • Next.js docs, Streaming: https://nextjs.org/docs/app/guides/streaming · next-streaming.txt. "Each boundary streams independently as its async work completes, in whatever order that happens". Supports step 8.
  • Next.js docs, connection, cookies, Revalidating, ISR (both models), How revalidation works, Migrating to Cache Components, Caching (previous model), Route segment config: saved, supporting steps 2, 3, 9–11 (route segment config dynamic is not used with Cache Components).

Checked against the tools and docs as of 2026-09-30.