cf.completefrontend
Open lab My progress
THE NEXT.JS FIELD GUIDE

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.

By the end, you can
  • 01
    Tell 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.
  • 02
    Follow 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.
  • 03
    Find 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 build runs: once, before the app takes visitors. Request time is when a visitor's request reaches the server.
  • After the build, next build prints 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.
Three windows from left to right. Build, once before any visit: render Home, keep the result, bundle chunks. Server, for each request: send the kept HTML, render the cart, stream it in. Browser: show the HTML, hydrate, use payloads for link navigations. Arrows lead from each window to the next.Once, before visitsbuildrender Homekeep the resultbundle chunksFor each requestserversend kept HTMLrender the cartstream it inIn the browserbrowsershow the HTMLhydrateuse payloadsthe same tea shop, in three places
Where and when each part of the tea shop is made. 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.

Playground: one visit, then one click
Who does whatOpening the shop
What the visitor seesafter step 1
tea-shop.example/

A blank page

React isn't running yet: clicks do nothing.

The page isn't complete yet.

Step 1 of 5
Browser: The visitor opens the shop

The visitor types the address. The browser asks the server for the page at /.

Each step is one thing that happens, in order, in the recorded tea shop. Opening the shop follows a first visit to /; Clicking Cart follows what happens next. The screens are from the recordings below. Times aren't to scale. Reset goes back to the first step of Opening the shop.

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.

Real-life analogyFlat-pack furniture

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 build renders 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
The main idea

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.

The tea shop: what was made whenNext.js
app/cart/
app/page.tsx
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>  );}
Recorded from Next.js 16.4.3
$ 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 content

The 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 with x-nextjs-cache: HIT, a header Next.js adds when it sends a copy it kept. The server didn't run Home for this request: it sent the HTML next build made. Every visitor gets that copy, until the page is made again.
  • /cart's HTML holds the nav, the heading and the fallback Loading your cart… first: the static shell the build made. CartItem reads a cookie on line 6 of app/cart/page.tsx, so the server rendered it for this request and streamed Your 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.

The home page's HTML, and its RSC payloadNext.js
app/cart/
app/page.tsx
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>  );}
Recorded from Next.js 16.4.3

GET /?_rsc=200

Sent with the request
rsc 1
Response headers
Vary rsc, next-router-state-tree, next-router-prefetch, next-router-segment-prefetch, Accept-Encoding
x-nextjs-cache HIT
x-nextjs-prerender 1
x-nextjs-stale-time 300
Cache-Control s-maxage=31536000
Content-Type text/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": what Home rendered on lines 8 and 12 of app/page.tsx, already run.
  • {"id":"green"}: the props of the AddToCart next to Green tea. The payload says where each button goes and what it gets.
  • A path under /_next/static/chunks/: the file with AddToCart'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.)

Three windows from left to right. Server: the Server Component Home, which waits for getTeas. An arrow to the payload window: an h1 Tea shop, an li Green tea, and a reference to AddToCart's code file, with the props id green. An arrow to the HTML window: Tea shop, Green tea $4, and an Add to cart button.Server ComponentserverHomeawait getTeas()RSC payloadpayloadh1 "Tea shop"li "Green tea"AddToCart: its fileHTMLHTMLTea shopGreen tea: $4Add to cart{"id":"green"}
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:

  1. Getting started: making a project, running next build and next start, and your first app.
  2. Routing and navigation: the app folder, layouts that stay, and the click.
  3. Server and Client Components: Home on the server, AddToCart in the browser, and the payload between them.
  4. Styling, assets and metadata: the CSS, images, fonts and titles in the HTML.
  5. Fetching and mutating data: what getTeas stands in for, and forms that save with Server Functions.
  6. Rendering and caching: ○ and ◐, the static shell, and what's kept and for how long.
  7. Building real apps: code that runs before a request reaches a page, sign-in, and security.
  8. Production: measuring, testing and deploying the whole journey.
  9. Architecture and the Pages Router: structuring big apps, and the older router many projects still use.
  10. 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.

  1. 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.
  2. When it was made. In the Network panel, click the page's own request and read its response headers: x-nextjs-cache: HIT means the page is a copy the build kept.
  3. The prefetch. Type _rsc in the Network panel's filter box, and scroll a link onto the screen. A request for the linked page appears, with rsc: 1 in its request headers and text/x-component in its response headers.
  4. 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 _rsc request does.

Sort these steps of the tea shop's visit by when they happen:

When does it happen?
  • Home renders the three teas, and its HTML is kept.
  • Turbopack bundles AddToCart into a JavaScript chunk.
  • The route table with ○ / and ◐ /cart is printed.
  • CartItem reads the cart cookie.
  • 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 Home was, and keeps the nav.
Try it yourself
0 of 8 correct

Sort each step: during next build, for each request on the server, or in the browser.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Common mix-ups

  • "Next.js renders every page when a request comes in." No. / was rendered once, during next 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 on Add to cart only changes state. A form that calls a Server Function sends a POST.

Each row is one step of the visit: where and when it happens, and what follows from it.

Where and when each step happens
StepWhere and whenWhat follows
Home renders the teasOn the server, during next build.The HTML and the payload for / are kept, and every visit gets the kept copy.
CartItem reads the cookieOn the server, for each request.Its part of /cart is streamed into the static shell the build made.
AddToCart gets its click handlerIn the browser, during hydration.Until then, the button is on the screen but does nothing.
The router asks for /cart's payloadIn 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.

Exercise 1 · Warm-upFind the kept 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?

Starter codeTSX
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>
  );
}

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upRead the answer's type

    The router asks the server for /cart with the header rsc: 1. Which content-type does the server answer with?

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeWhat the JavaScript leaves out

      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?

      Starter codeTSX
      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>
        );
      }
      

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeCount the steps

        In the journey Opening the shop, after which step does the visitor first see the three teas?

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeWhat stays on the screen

          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?

          Starter codeTSX
          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>
            );
          }
          

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengeA click without a prefetch

            In this layout, the Cart link has prefetch={false}. After hydration, the visitor clicks Cart. What does the server send back?

            Starter codeTSX
            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>
              );
            }
            

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              For each question, ask where it happens, when, and what travels.

              The How Next.js works quiz · 7 questionsScore: first tries count
              1. Question 1 of 7What does next build do with the tea shop's home page?

                Choose an answer to see the explanation.

              2. Question 2 of 7What does the RSC payload hold for AddToCart, a Client Component?

                Choose an answer to see the explanation.

              3. Question 3 of 7When does AddToCart's button start to respond to clicks?

                Choose an answer to see the explanation.

              4. Question 4 of 7The router asks for /cart with the header rsc: 1. Which content type comes back?

                Choose an answer to see the explanation.

              5. Question 5 of 7What happens to the nav when the visitor clicks Cart?

                Choose an answer to see the explanation.

              6. Question 6 of 7In the HTML of /cart, what comes first?

                Choose an answer to see the explanation.

              7. Question 7 of 7Which text is in the home page's JavaScript chunks?

                Choose an answer to see the explanation.

              Key takeaways

              • next build renders 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.

              Read it in Next.js's own code

              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.

              CompleteFrontend Clear concepts. Working examples.