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

Server Components in Next.js

Learn how Server Components work in Next.js: the default for every component, async, close to your data and secrets, and what they send to the browser.

By the end, you can
  • 01
    Tell which components run only on the serverKnow that layouts, pages and what they import are Server Components, unless an imported file starts with "use client" and so starts the client part.
  • 02
    Read data and secrets while renderingWrite async components that wait for data and use a key from the server's environment, and say what reaches the browser.
  • 03
    Read the errors at the edgeRecognize the build errors for state, Effects, event handlers, browser globals and class instances in a Server Component, and what each means.

A tea page that runs only on the server

In Next.js, a component you write runs on the server unless it's part of the client. A component like that is a Server Component: React's kind of component that runs only on the server. Its code never goes to the browser. How Next.js works followed one request through the tea shop, and The React you need for Next.js listed what each kind of component can do. Server Components, in the React course, shows the idea in React itself.

Here is the tea shop's home page. It's a real Next.js 16.4.3 project, built with next build and run with next start, and the result is a recording of what happened. Nothing in its files says "server". Open the Terminal tab.

The tea shop's home page, with a message from the serverNext.js
app/
app/page.tsx
import { getTeas } from "./teas";import TeaCard from "./tea-card"; export default async function Home() {  const teas = await getTeas();  console.log(`Home rendered with ${teas.length} teas`);  return (    <main>      <h1>Tea shop</h1>      {teas.map((tea) => (        <TeaCard key={tea.id} name={tea.name} price={tea.price} />      ))}    </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 ...Home rendered with 3 teasHome rendered with 3 teas✓ Generating static pages (3/3)  Finalizing page optimization ... Route (app)┌ ○ /└ ○ /_not-found ○  (Static)  prerendered as static content

In app/page.tsx, line 4 makes Home an async function, and line 5 waits for getTeas() from app/teas.ts, a stand-in for the shop's database. Line 6 prints a message with console.log. Lines 10 to 12 render a TeaCard for each tea; in app/tea-card.tsx, line 4 shows the tea's name and line 5 its price, like $4 a cup.

The Terminal tab shows Home rendered with 3 teas among the build's own lines, before ✓ Generating static pages: Home ran on the server, while the build made the page. It shows twice because, with Cache Components on, the build renders a page twice: a first time to fill its caches, and a second time to keep the result. (Caches are taught in the Rendering and caching stage.) Then ○ / says the page was prerendered as static content: its HTML was made ahead of time, and next start sends that copy.

Now open the Network tab. The first response is the page's HTML, with <h1>Tea shop</h1> and an <article> for each tea. Pick the second one, its RSC payload, asked for with rsc: 1. The RSC payload (RSC stands for React Server Components) is React's own description of what the Server Components rendered, with the props they pass to Client Components. React in the browser reads it, and inline scripts in the HTML carry it on a first visit. You can find "Green tea" and " a cup" in it. Both responses hold what the components rendered. Neither holds their code. The recording also searched every JavaScript file the page loads for Home rendered with, the text of line 6, and for a cup, from line 5 of TeaCard: neither is there. So the visitor's browser console never shows line 6's message. The code that prints it never arrives.

Two windows. Left, the server holds page.tsx, tea-card.tsx and teas.ts: their code stays there. An arrow goes to the right window, the browser, which holds only what they rendered: Tea shop, Green tea, and $4 a cup.On the serverserverpage.tsxtea-card.tsxteas.tstheir code stays hereIn the browserbrowserTea shopGreen tea$4 a cuponly what they rendered
Home, TeaCard and getTeas run on the server. The browser gets what they rendered, in the HTML and the RSC payload: the heading, and each tea's name and price. Their code isn't in any JavaScript file the page loads.
Real-life analogyA newspaper

A newspaper's reporters work in the newsroom. They call their sources, read their notes and write. What reaches your door is the printed page: the stories, not the notes or the phone numbers. A few parts of the paper are made for you to work on at home, like the crossword.

In real life: The newsroom, where reporters write the stories
In Next.js: The server, where Server Components run
In real life: The reporters' notes and their sources' phone numbers
In Next.js: A Server Component's code, and values it never renders or passes on, like a key
In real life: The printed paper at your door
In Next.js: The HTML and the RSC payload: what the components rendered
In real life: The crossword you fill in at home
In Next.js: A Client Component: the part that works in the browser
The main idea

In Next.js, components are Server Components unless a file with "use client" says otherwise. A Server Component runs only on the server, during the build or for a request. Its code, the code that loads its data, and any value it neither renders nor passes on stay there. The browser gets what it rendered, and the props it passes to Client Components.

Server Components are the default

You don't mark a Server Component. In the App Router, layouts and pages start as Server Components, and so does every component they import, unless the imported file starts with "use client": then the client part starts there. In the tea page, RootLayout is one because app/layout.tsx is a layout, Home is one because app/page.tsx is a page, and TeaCard is one because a Server Component imports it.

The client part starts at a file whose first line is "use client". That file's components, and everything the file imports, also go to the browser. The next lesson, Client Components, is about that line. One directive is easy to mix up with it: "use server" doesn't make Server Components. It marks Server Functions, functions that run on the server and that the browser can call. There is no directive for Server Components, because they're what you get without one.

Each row is one kind of file in the app folder: what it is, and why.

Which files are Server Components?
The fileWhat it isWhy
app/layout.tsx or app/page.tsxA Server Component.Layouts and pages start on the server. They need no directive.
app/tea-card.tsx, imported by the pageA Server Component.A component imported by a Server Component is one too, unless its file starts with "use client".
A file that starts with "use client"A Client Component.The directive starts the client part: its code also goes to the browser.
A file imported by that Client ComponentPart of the client too.Everything a client file imports goes to the browser with it, directive or not.
A file that starts with "use server"Neither: it holds Server Functions."use server" marks functions the browser can call. It doesn't make Server Components.

Server Components came with the App Router, in Next.js 13. The older Pages Router, in a pages folder, has none: there, every component's code goes to the browser and hydrates, and a page gets its server data from a function like getServerSideProps. Many projects still use it; Pages Router basics teaches enough to work in one.

Async components wait for their data

A Server Component can be an async function, like Home on line 4. Any Server Component can, not only pages. Here, each tea's stock comes from a small async component of its own, Stock. (A Client Component can't be async; it reads a promise with the use API instead.)

An async Stock component for each teaNext.js
app/
app/stock.tsx
import { getStock } from "./supplier"; export default async function Stock({ id }: { id: string }) {  const count = await getStock(id, process.env.SUPPLIER_KEY);  return <>{count > 0 ? `${count} in stock` : "Sold out"}</>;}
Recorded from Next.js 16.4.3
localhost:3000/Tea shop
Tea shop
  • Green tea: 12 in stock
  • Mint tea: Sold out
  • Lemon tea: 5 in stock

The page as the browser shows it when its HTML arrives, drawn with plain styles. Its JavaScript doesn't run here.

In app/stock.tsx, line 3 makes Stock async. Line 4 waits for getStock, from app/supplier.ts, with the tea's id. Line 5 renders 12 in stock, or Sold out when the count is 0. In app/page.tsx, line 5 waits for the teas, and line 12 puts a Stock in each list item. The page is finished when every Stock has its answer. Open the Browser tab: Green tea has 12 left, Mint tea is sold out and Lemon tea has 5.

The build printed ○ /, because these stand-ins answer at once: their promises are settled before React has to wait, so nothing suspends. A component suspends when it waits for work that isn't finished yet, like a database query that hasn't answered. With Cache Components on, as in every new project, uncached work that suspends needs one of two things: a Suspense boundary that says what to show while it waits, or "use cache", which keeps the result. How long the wait takes isn't what matters. Try it: each button is a version of the stock page, built with real Next.js. Before you click, guess which one fails.

Playground: what does the build do when a component waits?
The stock page, with this version of its dataNext.js
app/
app/teas.ts
// A stand-in for the shop's database. A real shop would read it here.export async function getTeas() {  return [    { id: "green", name: "Green tea", price: 4, cost: 1.5 },    { id: "mint", name: "Mint tea", price: 3, cost: 1.1 },    { id: "lemon", name: "Lemon tea", price: 3, cost: 0.9 },  ];}
Recorded from Next.js 16.4.3
$ next build▲ Next.js 16.4.3 (Turbopack)- Environments: .env- Cache Components enabled- Partial Prefetching enabled   Creating an optimized production build ...✓ Compiled successfully  Running TypeScript ...  Finished TypeScript ...  Collecting page data ...✓ Generating static pages (3/3)  Finalizing page optimization ... Route (app)┌ ○ /└ ○ /_not-found ○  (Static)  prerendered as static content
Try it yourself
Answers at once

getTeas on line 2 answers at once, so nothing suspends. Next.js renders the whole page during the build and prints ○ /.

Every version was built with next build from Next.js 16.4.3; the Terminal shows what it printed. Reset goes back to the first version.

The first version builds as ○ /. The second waits 50 ms for a promise nothing caches, and stops the build with Route "/": Next.js encountered uncached or runtime data during prerendering. The error lists three ways to fix it. The third waits just as long, but "use cache" on line 3 of app/teas.ts keeps the result: the build fills the cache and prints ○ /, with a Revalidate time of 15m beside it. The fourth takes the first fix: it moves the part that waits into TeaList and puts it inside Suspense. The build then prerenders the heading and Loading teas…, prints ◐ /, and the list is streamed in for each request. The Fetching and mutating data stage teaches these choices, and Rendering and caching teaches "use cache".

Reading data and secrets directly

A Server Component runs only on the server, so it can use what only the server has: a database, files on disk, a service inside your network, or a secret key. It reads them while it renders, with nothing in between.

Look at the stock example again. Line 4 of app/stock.tsx passes process.env.SUPPLIER_KEY to getStock. process.env holds the server's environment variables: settings the server gets from outside your code. Next.js loads them from the project's .env file, and line 1 of that file sets SUPPLIER_KEY. Click .env in the file tree to see it. In app/supplier.ts, line 3 refuses to answer without a key, the way a real supplier would. (The last lesson of this stage teaches .env files, and the NEXT_PUBLIC_ prefix for the few variables the browser may see.)

A client-rendered React app can't do this. Anyone can read the JavaScript a browser downloads, so the key can't be in it. You'd write an API on a server of your own that keeps the key and asks the supplier, and the browser would call that API after the page loads (Fetching data shows the browser side). In Next.js, Stock is that server code.

The recording checks that the key stayed on the server. It searched every JavaScript file the page loads for the key, sk_tea_7Hq2xP, and for the error text on line 3 of supplier.ts: neither is there. The page's markup and its RSC payload don't contain the key either. What they hold is what Stock rendered with it: 12 in stock.

Left, the server: a card SUPPLIER_KEY, and an arrow down to a card Stock: 12 in stock. An arrow goes right to the browser, which holds a card Green tea: 12 in stock, and a crossed-out card SUPPLIER_KEY: the key never arrives.On the serverserverSUPPLIER_KEYStock: 12 in stockthe key is used hereIn the browserbrowserGreen tea: 12 in stockSUPPLIER_KEYthe key never arrives
Stock reads SUPPLIER_KEY on the server and asks for the count with it. The browser gets Green tea: 12 in stock. The recording looked for the key in the page's JavaScript, its markup and its RSC payload, and found it in none of them.
A file isn't secret by itself

A Server Component keeps its own code on the server. The files it imports stay there only while Server Components alone import them. If a Client Component imported app/supplier.ts, that file's code would go to the browser too. The server-only package makes the build stop when that happens; the last lesson of this stage teaches it.

What reaches the browser, and what never does

Two things reach the browser from a Server Component:

  • What it rendered: the text and elements. They're in the HTML, for the first look, and in the RSC payload, for React.
  • The props it passes to Client Components. A Client Component also runs in the browser, so its props have to get there. They travel in the RSC payload.

Its code never does, nor the files that only Server Components import, nor the values it reads but doesn't render or pass on. Props are the easy part to miss. Here the tea list gives each tea to AddToCart, the Client Component from earlier lessons, which only reads the tea's id. Pick each version and read the payload in the Network tab.

Playground: what does the payload carry for each button?
The tea list, with this version of app/page.tsxNext.js
app/
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 tea={tea} />          </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/1tq6ppe__m8m-.js"
3:I[39756,["$2"],"default"]
4:I[37457,["$2"],"default"]
8:I[97367,["$2"],"OutletBoundary"]
9:"$Sreact.suspense"
c:"ViewportBoundary"
d:I[97367,["$2"],"$c"]
f:"MetadataBoundary"
10:I[97367,["$2"],"$f"]
13:I[68027,["$2"],"default",1]
5:X
b:X
6:[["children",{"s":"__PAGE__","h":514,"d":{"r":["$","$1","c",{"children":["$L7",[["$","script","script-0",{"src":"/_next/static/chunks/3vhjj7ffizy12.js","async":true,"nonce":"$undefined"}]],["$","$L8",null,{"children":["$","$9",null,{"name":"Next.MetadataOutlet","children":"$@a"}]}]]}],"p":true,"v":"$b"}}]]
12:X
14:X
15: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/1tq6ppe__m8m-.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",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":"$5"},"c":"$Q6"},"h":{"r":["$","$1","h",{"children":[null,["$","$Ld",null,{"children":"$Le"}],["$","$L10",null,{"children":[["$","div",null,{"hidden":true,"children":["$","$9",null,{"name":"Next.Metadata","children":"$L11"}]}],null]}],null]}],"p":true,"v":"$12"}},"m":"$undefined","G":["$13",[]],"S":true,"r":"$14","s":"$15","a":"$@16","l":"$undefined","p":"$undefined","d":"$undefined","b":"V2t28_2sDIDt9q1S714C4","u":"$@17"}
18:"/_next/static/chunks/3vhjj7ffizy12.js"
19:I[7870,["$2","$18"],"default"]
15:300
7:["$","main",null,{"children":[["$","h1",null,{"children":"Tea shop"}],["$","ul",null,{"children":[["$","li","green",{"children":["Green tea",": $",4," ",["$","$L19",null,{"tea":{"id":"green","name":"Green tea","price":4,"cost":1.5}}]]}],["$","li","mint",{"children":["Mint tea",": $",3," ",["$","$L19",null,{"tea":{"id":"mint","name":"Mint tea","price":3,"cost":1.1}}]]}],["$","li","lemon",{"children":["Lemon tea",": $",3," ",["$","$L19",null,{"tea":{"id":"lemon","name":"Lemon tea","price":3,"cost":0.9}}]]}]]}]]}]
e:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
a:null
11:[["$","title","0",{"children":"Tea shop"}]]
15:C
12:C
14:C
b:C
5:C
17:false
16:null
Try it yourself
Pass the whole tea

Line 12 passes each tea object. In the payload, each button's props hold id, name, price and cost, though AddToCart only reads id.

Both versions were built and run with Next.js 16.4.3. The Network tab shows the RSC payload, asked for with rsc: 1. Reset goes back to the first version.

In the first version, the payload holds "tea":{"id":"green","name":"Green tea","price":4,"cost":1.5}. That's the price the shop pays for its tea, which no visitor should see, and it went to every browser. Nothing on the page shows it, but anyone who opens the developer tools can read it. In the second version, each button gets only {"id":"green"}. Give a Client Component only what it needs. The next lessons of this stage teach which values can be props at all, and how to pass data across safely.

Two rows. Top: the page passes AddToCart tea equals tea, and the payload carries id, name, price and cost, though the button never reads cost. Bottom: the page passes AddToCart id equals tea.id, and the payload carries only id: green.The page passesThe payload carries<AddToCart tea={tea} />id, name, price, costcost too: the button never reads it<AddToCart id={tea.id} />id: "green"only what the button needs
A Client Component's props travel in the RSC payload, whole. Pass the whole tea, and its cost goes too. Pass tea.id, and only the id goes.

Each row is one thing from the tea shop's Server Components: does it reach the browser, and how do the recordings show it?

What reaches the browser
WhatDoes it reach the browser?What the recordings show
Home's and TeaCard's codeNo.None of the page's JavaScript files contains Home rendered with or a cup.
teas.ts and supplier.tsNo.Only Server Components import them, so they stay on the server.
What the components renderedYes, in the HTML and in the RSC payload.Tea shop and 12 in stock are in both.
Props passed to a Client ComponentYes, whole, in the RSC payload.Each tea's cost is there when the page passes the whole tea.
The supplier's keyNo.It's used on the server, and it's in neither the JavaScript, the page's markup nor the payload.
A console.log messageNo.It's printed in the terminal that runs next build.

One more thing never happens in the browser: a Server Component never hydrates. Hydration is React matching the Client Components to the HTML on the screen and attaching their event handlers. A Server Component has no code in the browser to match, so React uses what it rendered, from the payload, as it is.

What Server Components can't do

A Server Component runs on the server, gives back what it rendered, and is done. Its code never reaches the browser. So nothing that needs your code to keep running in the browser can go in it:

  • State and Effects: useState, useEffect and the other hooks that keep or react to changes after the page is on the screen.
  • Event handlers: onClick, onChange and the rest are functions the browser has to call.
  • Browser globals: window, document and localStorage exist only in the browser.
  • Props that aren't plain data: a Client Component's props travel in the payload, so they must be values React can send, not functions or class instances.

Next.js's docs add React context to the list. The browser can still do a lot on its own: a <details> element opens and closes, a link goes to its page, and a form can send its fields to a Server Function. The React you need for Next.js showed two of these errors. Here are all four kinds, in one tips page. Before you click, guess which versions build.

Playground: what can the tips page do as a Server Component?
The tips page, with this version of app/page.tsxNext.js
app/
app/page.tsx
export default function BrewingTips() {  return (    <main>      <h1>Brewing green tea</h1>      <details>        <summary>Show the tips</summary>        <p>Use water at 80 °C and steep for 2 minutes.</p>      </details>    </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 (3/3)  Finalizing page optimization ... Route (app)┌ ○ /└ ○ /_not-found ○  (Static)  prerendered as static content
Try it yourself
Tips in <details>

The <details> element on lines 5 to 8 opens and closes by itself in the browser. No JavaScript of yours is needed, and the build prints ○ /.

Every version was built with next build from Next.js 16.4.3; the Terminal shows what it printed. Reset goes back to the first version.

Only the first version builds. Each of the others stops the build, and the message says what went wrong. Each row is one thing the tips page tried, what the recorded build printed, and why:

What each error means
What the Server Component doesWhat the build printsWhy
Calls useState or useEffectYou're importing a module that depends on useState into a React Server Component module.Hooks for state and Effects need the component to keep running in the browser. A Server Component renders on the server and never runs in the browser.
Puts onClick on a <button>Event handlers cannot be passed to Client Component props.A handler is a function, and a Server Component's functions never leave the server.
Reads windowReferenceError: window is not definedwindow is the browser's. On the server there's no window to read.
Passes a class instance as a propOnly plain objects, and a few built-ins, can be passed to Client Components from Server Components.Props travel in the RSC payload as data. A class's methods are code, so they can't travel.

Notice when each error comes. useState and useEffect stop the build while it compiles: Next.js reads each file's imports and knows they only work in Client Components. The other three stop it later, while it prerenders the page and the code runs on the server. Either way, the fix is to move the part that needs the browser into a Client Component and keep the rest on the server. That's the next lesson.

Planning a tea page

When you build a page, start on the server, and ask what each part needs. Most of a tea page is text from the database: names, prices, descriptions, stock. Only a few parts react to the visitor: a cup counter, a button that remembers a choice. Sort these parts of a tea page:

Server Component, or Client Component?
  • Read the teas from the database while it renders.
  • Use the supplier's key from process.env.
  • Wait for data with await.
  • Show tips that open and close with <details>.
  • Count cups with useState.
  • Change document.title in useEffect.
  • Run code when a button is clicked.
  • Read the window's width.
Try it yourself
0 of 8 correct

Sort each part: can a Server Component do it, or does it need a Client Component?

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

Every card matches a recording in this lesson: the stock page read data with a key and waited with await, the <details> version built, and the state, Effect, click and window versions stopped the build. Read the data on the server, pass small props, and keep the Client Components small.

Common mix-ups

  • "A Server Component needs "use server"." No. It needs no directive at all. "use server" marks Server Functions.
  • "A Server Component runs again for every request." No. Home ran during next build, and next start sent the HTML the build kept. It runs again only when the page, or a part of it, is made again, like a part that reads the request.
  • "Server Components send nothing to the browser." No. What they render, and the props they pass to Client Components, are in the HTML and the RSC payload. Only their code stays behind.
  • "Server Components hydrate too." No. Their code isn't in the browser. React uses what they rendered, from the payload, and hydrates only the Client Components.
  • "A file that Server Components use is safe from the browser." No. If a Client Component imports the same file, its code goes to the browser too. Secrets belong in server-only modules and server environment variables.
  • "Any await is fine in a Server Component." No. With Cache Components on, uncached work that suspends needs Suspense around it or "use cache", or the build stops and says so. Cached work, and work that never suspends, is fine.

Practice exercises

These exercises are about Next.js projects, which can't run inside this page. Answer first, then use the hints.

Exercise 1 · Warm-upWhere the message goes

This is app/page.tsx. Where does the message on line 6 show up: in the terminal, or in the visitor's browser console?

Starter codeTSX
import { getTeas } from "./teas";
import TeaCard from "./tea-card";

export default async function Home() {
  const teas = await getTeas();
  console.log(`Home rendered with ${teas.length} teas`);
  return (
    <main>
      <h1>Tea shop</h1>
      {teas.map((tea) => (
        <TeaCard key={tea.id} name={tea.name} price={tea.price} />
      ))}
    </main>
  );
}

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

    Exercise 2 · Warm-upIs the card's code sent?

    This is app/tea-card.tsx from the first example. Is the text a cup from line 5 in the JavaScript the page loads?

    Starter codeTSX
    export default function TeaCard({ name, price }: { name: string; price: number }) {
      return (
        <article>
          <h2>{name}</h2>
          <p>${price} a cup</p>
        </article>
      );
    }
    

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

      Exercise 3 · PracticeFind the line that stops the build

      The tips page wants to change the tab's title. The build stops. Which line does the error point at?

      Starter codeTSX
      import { useEffect } from "react";
      
      export default function BrewingTips() {
        useEffect(() => {
          document.title = "Brewing green tea";
        }, []);
        return (
          <main>
            <h1>Brewing green tea</h1>
            <p>Use water at 80 °C and steep for 2 minutes.</p>
          </main>
        );
      }
      

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

        Exercise 4 · PracticeWhat the button's props carry

        AddToCart is a Client Component that only reads tea.id. Does the browser get each tea's cost?

        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 tea={tea} />
                  </li>
                ))}
              </ul>
            </main>
          );
        }
        

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

          Exercise 5 · ChallengeLet uncached data build

          With the uncached getTeas from the playground, which waits 50 ms, this page stops the build. Change it so the build passes, without "use cache". Which symbol does next build print for / then?

          Starter codeTSX
          import { getTeas } from "./teas";
          import Stock from "./stock";
          
          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}: <Stock id={tea.id} />
                    </li>
                  ))}
                </ul>
              </main>
            );
          }
          

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

            Exercise 6 · ChallengeFind the prop that can't travel

            The build stops with Only plain objects, and a few built-ins, can be passed to Client Components from Server Components. Which line causes it?

            Starter codeTSX
            import { Tea } from "./tea";
            import AddToCart from "./add-to-cart";
            
            export default function Home() {
              const tea = new Tea("green", "Green tea");
              return (
                <main>
                  <h1>{tea.label()}</h1>
                  <AddToCart tea={tea} />
                </main>
              );
            }
            

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

              Check your understanding

              For each question, ask where the code runs, and what the browser gets from it.

              The Server Components quiz · 7 questionsScore: first tries count
              1. Question 1 of 7In a new Next.js app, which of these is a Server Component?

                Choose an answer to see the explanation.

              2. Question 2 of 7Home calls console.log while it renders. Where does the message show up?

                Choose an answer to see the explanation.

              3. Question 3 of 7Stock reads process.env.SUPPLIER_KEY and renders how many cups are left. What does the browser get from it?

                Choose an answer to see the explanation.

              4. Question 4 of 7What happens when you build this page?

                Read the code, then predictTSX
                export default function BrewingTips() {
                  return (
                    <main>
                      <h1>Brewing green tea</h1>
                      <button onClick={() => alert("Use water at 80 °C and steep for 2 minutes.")}>
                        Show the tips
                      </button>
                    </main>
                  );
                }
                

                Choose an answer to see the explanation.

              5. Question 5 of 7A page awaits an uncached database query that suspends, with no Suspense around it and no "use cache". What does next build do?

                Choose an answer to see the explanation.

              6. Question 6 of 7In this page, AddToCart only reads tea.id. What reaches the browser for each tea's button?

                Read the code, then predictTSX
                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 tea={tea} />
                          </li>
                        ))}
                      </ul>
                    </main>
                  );
                }
                

                Choose an answer to see the explanation.

              7. Question 7 of 7Which of these can a Server Component do?

                Choose an answer to see the explanation.

              Key takeaways

              • Layouts, pages and the components they import are Server Components, with no directive, unless an imported file starts with "use client". A file with "use client" starts the client part; "use server" marks Server Functions.
              • A Server Component can be async and wait for data while it renders. With Cache Components on, uncached work that suspends needs Suspense or "use cache", and the build says so.
              • It can read databases, files and secrets directly. Its code, its data-loading code, and values it neither renders nor passes on stay on the server; its messages print in the server's terminal.
              • The browser gets what it rendered, in the HTML and the RSC payload, and the props it passes to Client Components, whole. Pass only what a Client Component needs.
              • It can't use state, Effects, event handlers, browser globals or props that aren't plain data, and it never hydrates. The build stops and names the problem.

              Remember the one-liner.
              A Server Component runs on the server and sends only its result; anything that needs the browser goes in a Client Component.

              Read it in Next.js's own code

              These links point at Next.js v16.4.3, the version every example was built with. The list of React APIs a Server Component file can't import, with useState, useEffect and createContext, is at lines 648 to 697 of react_server_components.rs, and the message it prints is on line 347. Why a page renders twice during the build is explained in the comment at lines 9138 to 9150 of app-render.tsx: the first render fills the caches, and the second is the one Next.js keeps. The Under the hood stage reads them.

              Coming next: Client Components. It starts the client part with "use client", and shows what that line really marks.

              CompleteFrontend Clear concepts. Working examples.