How Next.js works: a first look
Follow one visit to a Next.js app: the build, the server's HTML and RSC payload, hydration, and the payload a link navigation uses. A map of the course.
- 01Tell build time from request timeSay which parts of a page Next.js makes during
next build, and which it makes when a request comes in, from the build's output and the headers. - 02Follow a page from the server to the browserDescribe what the server sends (HTML and the RSC payload), what hydration does, and what travels when a link is clicked.
- 03Find each part in the courseKnow which stage of the course teaches each step, so the rest of it fits on one map.
One visit to the tea shop, from build to click
A Next.js app does its work in three places, at two different times. During next build, before anyone visits, it renders every part of every page it can. When a request comes in, the server renders the rest and sends it. In the visitor's browser, React makes the page respond to clicks, and a click on a link to another page brings only what that page needs. This lesson follows one visit to the tea shop through all of it, at first-look depth. Each part has a stage of the course that teaches it in full.
The shop is the one from Why Next.js?, file for file: a home page that lists three teas, each with an Add to cart button, a cart page that reads a cookie, and a layout with links to both. The pieces of React it uses are the ones The React you need for Next.js reminded you of.
A few words this lesson uses, in plain terms first:
- Build time is when
next buildruns: once, before the app takes visitors. Request time is when a visitor's request reaches the server. - After the build,
next buildprints a symbol next to each route.○means prerendered as static content: its HTML was made ahead of time.◐means prerendered as static HTML with dynamic server-streamed content: a shell made ahead of time, with a part made for each request. - The RSC payload (RSC stands for React Server Components) is React's own description of what the Server Components rendered. It travels next to the HTML, and React in the browser reads it.
- Hydration is React, in the browser, matching the Client Components to the HTML already on the screen and attaching their event handlers, so buttons start to work.
next build renders what it can ahead of time, and keeps the HTML and the payload. The server does the rest for each request. In the browser, React hydrates the page, and a later <Link> navigation uses an RSC payload.Step through it below. Pick Opening the shop, press Step, and watch the right side: what the visitor sees after each step, and whether the page responds yet. Then pick Clicking Cart.
Opening the shopafter step 1A blank page
React isn't running yet: clicks do nothing.
The page isn't complete yet.
The visitor types the address. The browser asks the server for the page at /.
The visitor sees the teas after step 2, before any JavaScript has run, and the buttons respond after step 4. When the visitor then clicks Cart, the router can reuse what it prefetched in step 5; in the recording below, the click sent no request at all. The rest of this lesson opens each step, with a recording of the real app as proof.
A furniture shop cuts most parts at the factory, long before anyone orders. A few parts are made to measure when an order comes in. Everything ships in one box with an instruction sheet, and the last steps happen in the customer's home. When a drawer needs replacing later, the shop sends just the drawer.
- In real life: Parts cut at the factory before anyone orders
- In Next.js: What
next buildrenders ahead of time:○ - In real life: A part made the day an order comes in
- In Next.js: What the server renders for each request: the cart's part of
◐ - In real life: The instruction sheet packed in the box
- In Next.js: The RSC payload: a description of what was rendered
- In real life: Putting the moving parts together at home
- In Next.js: Hydration: React attaches the Client Components in the browser
- In real life: A replacement drawer sent on its own, not a new cupboard
- In Next.js: A
<Link>navigation: only the new page's payload is used
Next.js renders on the server, at build time when it can and at request time when it must, and sends HTML plus an RSC payload. In the browser, React hydrates the Client Components. After that, a <Link> navigation uses a payload the router prefetched, or fetches one. The HTML is for the first look. The payload is for React: what the Server Components rendered, and where each Client Component goes. Other clicks, like a button that changes state, may fetch nothing at all.
Build time and request time
Next.js renders everything it can at build time and keeps the result in the project's .next folder. Only what depends on the request, like a cookie, waits for request time. Here is the shop, built and run with Next.js 16.4.3. Open the Terminal tab.
app/page.tsximport { getTeas } from "./teas";import AddToCart from "./add-to-cart"; export default async function Home() { const teas = await getTeas(); return ( <main> <h1>Tea shop</h1> <ul> {teas.map((tea) => ( <li key={tea.id}> {tea.name}: ${tea.price} <AddToCart id={tea.id} /> </li> ))} </ul> </main> );}$ next build▲ Next.js 16.4.3 (Turbopack)- Cache Components enabled- Partial Prefetching enabled Creating an optimized production build ...✓ Compiled successfully Running TypeScript ... Finished TypeScript ... Collecting page data ...✓ Generating static pages (4/4) Finalizing page optimization ... Route (app)┌ ○ /├ ○ /_not-found└ ◐ /cart ○ (Static) prerendered as static content◐ (Partial Prerender) prerendered as static HTML with dynamic server-streamed contentThe route table says ○ / and ◐ /cart, and Why Next.js? explained how Next.js picks them. Now look at when each part was made. Open the Network tab:
/comes withx-nextjs-cache: HIT, a header Next.js adds when it sends a copy it kept. The server didn't runHomefor this request: it sent the HTMLnext buildmade. Every visitor gets that copy, until the page is made again./cart's HTML holds the nav, the heading and the fallbackLoading your cart…first: the static shell the build made.CartItemreads a cookie on line 6 ofapp/cart/page.tsx, so the server rendered it for this request and streamedYour cart is empty.further down in the same response.
So one page can be made at two times. The course's Rendering and caching stage teaches how Next.js decides, how long a kept copy stays, and how to make it again when the data changes.
The server sends HTML and an RSC payload
When the server renders a page, at build time or request time, React first runs the Server Components and writes down what they rendered: the RSC payload. Where a Client Component goes, the payload holds a reference to the file with its code, and the props it gets, not the code itself. Then the server makes the HTML from the payload, rendering the Client Components too. Both go to the browser: the HTML to show at once, and the payload for React.
Here is one glimpse of a payload. Next.js's router asks for one with the header rsc: 1, and the recording asked for the home page's the same way. (Ignore the ?_rsc= in the address; The RSC payload, in the Under the hood stage, explains it.) Pick the second request above the page, its RSC payload, asked for with rsc: 1, and open the Network tab.
app/page.tsximport { getTeas } from "./teas";import AddToCart from "./add-to-cart"; export default async function Home() { const teas = await getTeas(); return ( <main> <h1>Tea shop</h1> <ul> {teas.map((tea) => ( <li key={tea.id}> {tea.name}: ${tea.price} <AddToCart id={tea.id} /> </li> ))} </ul> </main> );}GET /?_rsc=200
- Sent with the request
rsc1
- Response headers
Varyrsc, next-router-state-tree, next-router-prefetch, next-router-segment-prefetch, Accept-Encodingx-nextjs-cacheHITx-nextjs-prerender1x-nextjs-stale-time300Cache-Controls-maxage=31536000Content-Typetext/x-component
The body that arrived, as it arrived: the RSC payload, one row per line
#1:"$Sreact.fragment"
2:"/_next/static/chunks/3oh00mkbkq8u6.js"
3:I[22016,["$2"],""]
4:I[39756,["$2"],"default"]
5:I[37457,["$2"],"default"]
9:I[97367,["$2"],"OutletBoundary"]
a:"$Sreact.suspense"
d:"ViewportBoundary"
e:I[97367,["$2"],"$d"]
10:"MetadataBoundary"
11:I[97367,["$2"],"$10"]
14:I[68027,["$2"],"default",1]
6:X
c:X
7:[["children",{"s":"__PAGE__","h":514,"d":{"r":["$","$1","c",{"children":["$L8",[["$","script","script-0",{"src":"/_next/static/chunks/37eqwody764mi.js","async":true,"nonce":"$undefined"}]],["$","$L9",null,{"children":["$","$a",null,{"name":"Next.MetadataOutlet","children":"$@b"}]}]]}],"p":true,"v":"$c"}}]]
13:X
15:X
16:X
0:{"P":null,"c":["",""],"q":"","i":false,"t":{"t":{"s":"","h":530,"d":{"r":["$","$1","c",{"children":[[["$","script","script-0",{"src":"/_next/static/chunks/3oh00mkbkq8u6.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":[["$","nav",null,{"children":[["$","$L3",null,{"href":"/","children":"Teas"}]," ",["$","$L3",null,{"href":"/cart","children":"Cart"}]]}],["$","$L4",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]]}]}]]}],"p":true,"v":"$6"},"c":"$Q7"},"h":{"r":["$","$1","h",{"children":[null,["$","$Le",null,{"children":"$Lf"}],["$","$L11",null,{"children":[["$","div",null,{"hidden":true,"children":["$","$a",null,{"name":"Next.Metadata","children":"$L12"}]}],null]}],null]}],"p":true,"v":"$13"}},"m":"$undefined","G":["$14",[]],"S":true,"r":"$15","s":"$16","a":"$@17","l":"$undefined","p":"$undefined","d":"$undefined","b":"figMnJ5F1z6h6PKyzB5FA","u":"$@18"}
19:"/_next/static/chunks/37eqwody764mi.js"
1a:I[7870,["$2","$19"],"default"]
16:300
8:["$","main",null,{"children":[["$","h1",null,{"children":"Tea shop"}],["$","ul",null,{"children":[["$","li","green",{"children":["Green tea",": $",4," ",["$","$L1a",null,{"id":"green"}]]}],["$","li","mint",{"children":["Mint tea",": $",3," ",["$","$L1a",null,{"id":"mint"}]]}],["$","li","lemon",{"children":["Lemon tea",": $",3," ",["$","$L1a",null,{"id":"lemon"}]]}]]}]]}]
f:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
b:null
12:[["$","title","0",{"children":"Tea shop"}]]
16:C
13:C
15:C
c:C
6:C
18:false
17:null
The same address answered with content-type: text/x-component, not HTML. You don't need to read the format yet. Look for three things in it:
"Tea shop"and"Green tea": whatHomerendered on lines 8 and 12 ofapp/page.tsx, already run.{"id":"green"}: the props of theAddToCartnext to Green tea. The payload says where each button goes and what it gets.- A path under
/_next/static/chunks/: the file withAddToCart's code, which the browser downloads.
There's no getTeas and no Home function anywhere in it: a Server Component's code never leaves the server, only what it rendered. On a first visit, the payload doesn't need a request of its own: inline <script> tags at the end of the HTML deliver it with the page. (The Network tab leaves their code out.)
Home runs on the server and renders into the RSC payload: what it rendered, plus a reference to AddToCart (the file with its code, and each button's props). The server then makes the HTML from the payload, rendering AddToCart too. Both are sent; the browser shows the HTML, and React reads the payload.The course's Server and Client Components stage teaches how to split a page between the two, and The RSC payload, in the Under the hood stage, reads the payload in detail.
The browser shows the HTML, then React hydrates
The browser shows the HTML as soon as it arrives: the teas are on the screen after step 2 of the journey, before any JavaScript has run. The Add to cart buttons are there too, but they do nothing yet: they're plain HTML.
Then the browser downloads the JavaScript files the HTML's <script> tags point at: React, Next.js's router, and AddToCart. The recording looked through every one of them that / loads: they contain the text Add to cart, from AddToCart, but not Green tea, which only getTeas on the server knows.
Once the code is there, React hydrates the page. It reads the RSC payload that came with the HTML, renders the Client Components, and matches them to the HTML already on the screen. Where they match, React reuses that HTML instead of making it again, and attaches the event handlers. Each AddToCart gets its onClick and its added state, and the page responds. Server Components don't hydrate: their part of the page is finished, and React only needs what they rendered, from the payload. Hydration, in the React course, shows what React checks while it does this, and what happens when the HTML doesn't match.
Go back to the journey above and step through Opening the shop again: step 3 is the download, and step 4 is hydration, where the line under the screen changes to "the page responds to clicks".
Where the course teaches each part
Every stage of the course opens one part of this journey. Here they are, in order, with the part each one opens:
- Getting started: making a project, running
next buildandnext start, and your first app. - Routing and navigation: the
appfolder, layouts that stay, and the click. - Server and Client Components:
Homeon the server,AddToCartin the browser, and the payload between them. - Styling, assets and metadata: the CSS, images, fonts and titles in the HTML.
- Fetching and mutating data: what
getTeasstands in for, and forms that save with Server Functions. - Rendering and caching:
○and◐, the static shell, and what's kept and for how long. - Building real apps: code that runs before a request reaches a page, sign-in, and security.
- Production: measuring, testing and deploying the whole journey.
- Architecture and the Pages Router: structuring big apps, and the older router many projects still use.
- Under the hood: Next.js's own source code for every step: the build, the server, the payload and the router.
Watch it happen in your own browser
You can see every step of this lesson in any Next.js app, with your browser's developer tools. Use a production build: next build, then next start. In development, with next dev, links don't prefetch, and pages are rendered as you ask for them, so the headers differ.
- The first HTML. Open the page, then View Page Source. The content is in the HTML. Near the end, inline
<script>tags carry the RSC payload. - When it was made. In the Network panel, click the page's own request and read its response headers:
x-nextjs-cache: HITmeans the page is a copy the build kept. - The prefetch. Type
_rscin the Network panel's filter box, and scroll a link onto the screen. A request for the linked page appears, withrsc: 1in its request headers andtext/x-componentin its response headers. - The click. Clear the panel and click the link. No new HTML page loads, and the page doesn't flash white: React swapped one part of it. If the router had prefetched everything the page needs, no request appears at all; otherwise, one
_rscrequest does.
Sort these steps of the tea shop's visit by when they happen:
Homerenders the three teas, and its HTML is kept.- Turbopack bundles
AddToCartinto a JavaScript chunk. - The route table with
○ /and◐ /cartis printed. CartItemreads thecartcookie.- The server sends
/'s kept HTML to a visitor. - React attaches
AddToCart's click handler. - The router asks for
/cart's payload when the Cart link comes on the screen. - React puts the cart page where
Homewas, and keeps the nav.
Sort each step: during next build, for each request on the server, or in the browser.
Common mix-ups
- "Next.js renders every page when a request comes in." No.
/was rendered once, duringnext build, and every visit gets that copy (x-nextjs-cache: HIT). Only the parts that read the request wait for it. - "Clicking a link loads a new HTML page." No. After hydration, a
<Link>navigation uses the new page's RSC payload, prefetched or fetched at the click, and React updates only the part that changed. - "The RSC payload is my data, as JSON." No. It's React's description of what the Server Components rendered, in its own format, with references to Client Components. Data shows up in it only as what was rendered.
- "Hydration draws the page again." No. React renders the Client Components and matches them to the HTML that's already there, reusing it, and adds the event handlers. When they match, the visitor sees no change.
- "Server Components hydrate too." No. Their code never reaches the browser. React needs only what they rendered, which is in the payload.
- "Every click sends a request." No. In the recording, the click on Cart sent none, because the router had prefetched
/cart. A click onAdd to cartonly changes state. A form that calls a Server Function sends aPOST.
Each row is one step of the visit: where and when it happens, and what follows from it.
| Step | Where and when | What follows |
|---|---|---|
Home renders the teas | On the server, during next build. | The HTML and the payload for / are kept, and every visit gets the kept copy. |
CartItem reads the cookie | On the server, for each request. | Its part of /cart is streamed into the static shell the build made. |
AddToCart gets its click handler | In the browser, during hydration. | Until then, the button is on the screen but does nothing. |
The router asks for /cart's payload | In the browser, when the Cart link comes on the screen. | A click on Cart can then reuse it. In the recording, the click sent no request. |
Practice exercises
These exercises are about a Next.js app, which can't run inside this page.
This is the shop's app/page.tsx. Which of the shop's addresses gets the HTML that next build kept, for every visitor?
import { getTeas } from "./teas";
import AddToCart from "./add-to-cart";
export default async function Home() {
const teas = await getTeas();
return (
<main>
<h1>Tea shop</h1>
<ul>
{teas.map((tea) => (
<li key={tea.id}>
{tea.name}: ${tea.price} <AddToCart id={tea.id} />
</li>
))}
</ul>
</main>
);
}
/. Home, in app/page.tsx, reads nothing from the request, so next build rendered it once, and the recorded response says x-nextjs-cache: HIT. /cart reads a cookie, so a part of it waits for each request.
The router asks the server for /cart with the header rsc: 1. Which content-type does the server answer with?
text/x-component: the RSC payload. Both recorded payload responses have it, while the same addresses without rsc: 1 answer with text/html.
This is app/page.tsx. Both Green tea and Add to cart are on the page. Which of the two is not in the JavaScript files the page loads?
import { getTeas } from "./teas";
import AddToCart from "./add-to-cart";
export default async function Home() {
const teas = await getTeas();
return (
<main>
<h1>Tea shop</h1>
<ul>
{teas.map((tea) => (
<li key={tea.id}>
{tea.name}: ${tea.price} <AddToCart id={tea.id} />
</li>
))}
</ul>
</main>
);
}
Green tea. It comes from getTeas(), which only runs on the server, so it reaches the browser only as rendered text, in the HTML and the payload. Add to cart is in AddToCart's code, which the browser downloads. The recording looked through every JavaScript file / loads.
In the journey Opening the shop, after which step does the visitor first see the three teas?
After step 2, when Next.js sends the HTML the build kept. The JavaScript arrives in step 3 and React hydrates in step 4, but the teas were on the screen already.
This is the shop's app/layout.tsx. When the visitor, on /, clicks Cart, which part of what it renders stays on the screen as it is?
import Link from "next/link";
export const metadata = { title: "Tea shop" };
export default function RootLayout({ children }: LayoutProps<"/">) {
return (
<html lang="en">
<body>
<nav>
<Link href="/">Teas</Link> <Link href="/cart">Cart</Link>
</nav>
{children}
</body>
</html>
);
}
The nav, with Teas and Cart. Both pages share the root layout, so React keeps it and swaps only {children}, the page part. In the recording, the page after the click still starts with Teas Cart, then Your cart.
In this layout, the Cart link has prefetch={false}. After hydration, the visitor clicks Cart. What does the server send back?
import Link from "next/link";
export const metadata = { title: "Tea shop" };
export default function RootLayout({ children }: LayoutProps<"/">) {
return (
<html lang="en">
<body>
<nav>
<Link href="/">Teas</Link> <Link href="/cart" prefetch={false}>Cart</Link>
</nav>
{children}
</body>
</html>
);
}
An RSC payload, with content-type: text/x-component. In the recording, opening / made no prefetch for /cart. The click then made one request, sent with rsc: 1, and the answer was text/x-component, not a new HTML page.
Check your understanding
For each question, ask where it happens, when, and what travels.
Question 1 of 7What does
next builddo with the tea shop's home page?Choose an answer to see the explanation.
Question 2 of 7What does the RSC payload hold for
AddToCart, a Client Component?Choose an answer to see the explanation.
Question 3 of 7When does
AddToCart's button start to respond to clicks?Choose an answer to see the explanation.
Question 4 of 7The router asks for
/cartwith the headerrsc: 1. Which content type comes back?Choose an answer to see the explanation.
Question 5 of 7What happens to the nav when the visitor clicks Cart?
Choose an answer to see the explanation.
Question 6 of 7In the HTML of
/cart, what comes first?Choose an answer to see the explanation.
Question 7 of 7Which text is in the home page's JavaScript chunks?
Choose an answer to see the explanation.
Key takeaways
next buildrenders everything it can ahead of time and keeps it. The server renders only what depends on the request, and streams it into the kept shell.- The server renders Server Components into the RSC payload, then makes the HTML from it. A first visit gets both in one response.
- The payload holds what the Server Components rendered, and references to the Client Components with their props. Server Components' code never reaches the browser.
- The browser shows the HTML at once. Then React hydrates: it matches the Client Components to the HTML and attaches their event handlers, and the page responds. Server Components don't hydrate.
- After that, a
<Link>navigation uses an RSC payload the router prefetched, or fetches one at the click. React swaps the part that changed and keeps the layout. Other clicks may fetch nothing.
Remember the one-liner.
Build what you can, render the rest per request, send HTML plus a payload, hydrate once, then navigate with payloads.
The header names are in app-router-headers.ts: RSC_HEADER = 'rsc' on line 1, and RSC_CONTENT_TYPE_HEADER = 'text/x-component' on line 16. The scripts that carry the payload inside the HTML are written in use-flight-response.tsx, as self.__next_f.push(…) on line 270. The course's last stage reads them in full.
Coming next: Creating a Next.js app. It starts a project with create-next-app, so you can run next build and next start and watch all of this yourself.