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

The React you need for Next.js

A refresher on the React that Next.js code uses every day: components, state, Effects, Suspense, Server Components and form actions, with live examples.

By the end, you can
  • 01
    Spot the React in a Next.js pageName the React behind each part of a page: components, props, state, Effects, Suspense, use, Server Functions and form actions.
  • 02
    Tell what runs whereSay which React features need a Client Component, which work in a Server Component, and what one can pass to the other.
  • 03
    Find the lesson behind each ideaKnow which React lesson teaches each part in full, so you can read it before the Next.js lessons that use it.

A Next.js page is React you already know

Every page, layout and button in a Next.js app is a React component. Next.js adds files, folders and a server around them, but the code inside is React. This lesson is a quick refresher on the React that Next.js code uses every day, and on the few places where Next.js changes how it behaves. It doesn't teach React from the start: each section links to the React lessons that do.

Here is one page of the tea shop from Why Next.js?, as a real Next.js 16.4.3 project. It was built with next build, run with next start, and what it sent back was recorded. Click a file in the tree to read it.

A tea shop page: a Server Component and a Client ComponentNext.js
app/
app/page.tsx
import { getTeas } from "./teas";import TeaCard from "./tea-card"; export default async function Home() {  const teas = await getTeas();  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
localhost:3000/Tea shop
Tea shop
Green tea

$4 a cup

Add to cart
Mint tea

$3 a cup

Add to cart
Lemon tea

$3 a cup

Add to cart

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

Almost every line is React:

  • Home, on line 4 of app/page.tsx, is a component: a function that returns markup.
  • Line 5 waits for getTeas(), because Home is an async Server Component. It runs only on the server.
  • Line 10 makes a TeaCard for each tea with map and a key, and hands it two props: name and price.
  • In app/tea-card.tsx, line 6 keeps a count in state, and line 11 changes it in an event handler.
  • Line 1 of app/tea-card.tsx is "use client". That's the one line here that isn't everyday React. It marks where the part of the app that also runs in the browser begins, so the card can keep state and handle clicks.

Open the Network tab: the HTML already holds the heading and the three cards, each with an Add to cart button. Both components ran on the server to make that HTML. Only TeaCard's code also goes to the browser, so the button works there.

Two editor windows. Left, page.tsx, a Server Component: async function Home, and await getTeas(); it runs only on the server. Right, tea-card.tsx, a Client Component: useState(0) and onClick; its HTML is made on the server, then it runs in the browser. An arrow from the page to the card carries the props name and price.Server Componentpage.tsxasync function Home()await getTeas()runs only on the serverprops:name, priceClient Componenttea-card.tsxuseState(0)onClickHTML on the server,then it runs in the browser
The overview's two files. Home is a Server Component: it can be async and wait for data, and it runs only on the server. TeaCard starts with "use client", so it can keep state and handle clicks. The page hands it two props.
Real-life analogyThe same recipes, a bigger kitchen

A cook who moves to a restaurant keeps the same recipes. What's new is where each step happens: most of the cooking in the back room, and a few last touches at the counter, where the guest can watch and ask for changes.

In real life: Recipes you already know
In Next.js: Components, props, state, Effects, Suspense
In real life: A kitchen with a back room and a front counter
In Next.js: The server and the browser
In real life: Dishes cooked in the back, ready before the guest sees them
In Next.js: Server Components, which can wait for data
In real life: The last touch made at the counter, in front of the guest
In Next.js: Client Components, which handle clicks and state
The main idea

Next.js code is React code. Components, props, state, Effects, Suspense, use and form actions work as the React course teaches them. What Next.js adds is where each one runs: components start as Server Components on the server, and a file that starts with "use client" marks the part that also runs in the browser, where state, clicks and Effects live.

If any of these feel new, read its React lesson first. Here they all are, in the order this lesson reminds you of them:

Components, props and state

Here is the tea card again, this time as a client-rendered React example, like a new Vite app: React draws it in the browser, so it runs right in this page. Click Add to cart on Mint tea twice, then once on Green tea.

Three tea cards, each with its own countPop out in the code editor (opens in a new tab)JSX
import TeaCard from "./TeaCard.jsx"; const teas = [  { id: "green", name: "Green tea", price: 4 },  { id: "mint", name: "Mint tea", price: 3 },  { id: "lemon", name: "Lemon tea", price: 3 },]; export default function App() {  return (    <main>      <h1>Tea shop</h1>      {teas.map((tea) => (        <TeaCard key={tea.id} name={tea.name} price={tea.price} />      ))}    </main>  );}
ResultLoading…

Mint tea's button says In cart: 2, and Green tea's says In cart: 1. Lemon tea's still says Add to cart. A quick reminder of why:

  • A component is a function whose name starts with a capital letter and that returns JSX. App on line 9 is one, and so is TeaCard in the TeaCard.jsx tab.
  • Props are the inputs a parent hands a child. Line 14 hands each card a name and a price, and line 3 of TeaCard.jsx takes them out with destructuring. A component never changes its own props.
  • State is a component's memory. Line 4 of TeaCard.jsx gives each card its own count, so the three counts don't mix. Calling setCount on line 9 asks React to run the component again with the new value, and React updates the button.
  • The key on line 14 tells React which card is which when the list changes (Rendering lists).

What changes in Next.js. The card is the same code, with types and one more line. In the overview's app/tea-card.tsx, line 1 is "use client": state and clicks only work in Client Components. The list itself moved into Home, a Server Component, which hands each card its props. Props that go from a Server Component to a Client Component travel from the server to the browser, so they have to be data: text, numbers, plain objects and arrays. The section on Server Components shows what else can travel. Sharing state and Thinking in React teach where state should live; in Next.js, that place has to be a Client Component.

Effects, and when you don't need one

An Effect is code that React runs after a component is on the screen, to keep it in step with something outside React: a timer, a browser event, a connection. This one keeps a number in step with the size of the window. The result frame is a small window of its own.

An Effect that measures the windowPop out in the code editor (opens in a new tab)JSX
import { useEffect, useState } from "react"; export default function WindowWidth() {  const [width, setWidth] = useState(null);   useEffect(() => {    function measure() {      setWidth(window.innerWidth);    }    measure();    window.addEventListener("resize", measure);    return () => window.removeEventListener("resize", measure);  }, []);   return <p>{width === null ? "Measuring your window…" : `Your window is ${width} px wide.`}</p>;}
ResultLoading…

Line 4 starts width at null. After React has put the paragraph on the screen, it runs the Effect on lines 6 to 13: line 10 measures the window, and line 11 measures it again whenever it's resized. Line 12 returns a cleanup function, which React calls when the component goes away, so the listener doesn't stay behind. The empty array on line 13 says the Effect depends on nothing, so it runs once. The result says Your window is … px wide. with the result frame's width; make your browser window narrower to watch it change.

What changes in Next.js. Effects run only in the browser. Next.js makes a Client Component's HTML on the server without running its Effects, because the server has no screen and no window. Here is the same component in a Next.js page. Open the Browser tab:

The same component, as the server's HTML has itNext.js
app/
app/window-width.tsx
"use client"; import { useEffect, useState } from "react"; export default function WindowWidth() {  const [width, setWidth] = useState<number | null>(null);   useEffect(() => {    function measure() {      setWidth(window.innerWidth);    }    measure();    window.addEventListener("resize", measure);    return () => window.removeEventListener("resize", measure);  }, []);   return <p>{width === null ? "Measuring your window…" : `Your window is ${width} px wide.`}</p>;}
Recorded from Next.js 16.4.3
localhost:3000/Tea shop
Tea shop

Measuring your window…

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

The recorded page says Measuring your window…: the HTML was made during next build, with width still null from line 6 of app/window-width.tsx, so line 17 chose the first text. In the browser, React runs the Effect after hydration: React matches the Client Components to the HTML that's already on the screen and attaches their event handlers. Then the width appears. A Server Component can't have Effects at all: they need a browser to run in.

Many Effects aren't needed. An Effect that only works out a value from props or state does extra work: React draws the page, runs the Effect, then draws it again with the new value. Work the value out while rendering instead:

A total worked out while renderingPop out in the code editor (opens in a new tab)JSX
import { useState } from "react"; const prices = { green: 4, mint: 3, lemon: 3 }; export default function App() {  const [cart, setCart] = useState(["green", "mint"]);  const total = cart.reduce((sum, id) => sum + prices[id], 0);   return (    <>      <p>        {cart.length} teas, total ${total}      </p>      <button onClick={() => setCart([...cart, "lemon"])}>Add lemon tea</button>    </>  );}
ResultLoading…

Line 7 adds up the prices each time App runs, so total is always right: 2 teas, total $7, and after a click on Add lemon tea, 3 teas, total $10. This is the version to avoid; it isn't a whole app, so it doesn't run on its own:

The same total, copied into state by an Effect (avoid this)JSX
// Avoid: an Effect that copies one state into another
const [total, setTotal] = useState(0);
useEffect(() => {
  setTotal(cart.reduce((sum, id) => sum + prices[id], 0));
}, [cart]);

In Next.js there's one more Effect you rarely need: the one that loads a page's data. In a client-rendered React app, a component can start a request in an Effect after it's on the screen (Fetching data shows that, and better ways). In Next.js, an async Server Component waits for the data on the server, like Home in the overview, so the data is already in the HTML. Effects and You might not need an Effect teach both sides in full.

Suspense and the use API

Suspense shows a fallback, like a loading message, while a part of the page waits for something. The use API reads the value of a promise inside a component. If the promise hasn't settled yet, the component waits, and the nearest Suspense shows its fallback. Press Run again on the result to watch it.

A list that waits for its teasPop out in the code editor (opens in a new tab)JSX
import { Suspense, use } from "react";import { getTeas } from "./api.js"; const teasPromise = getTeas(); function TeaList({ teasPromise }) {  const teas = use(teasPromise);  return (    <ul>      {teas.map((tea) => (        <li key={tea.id}>{tea.name}</li>      ))}    </ul>  );} export default function App() {  return (    <main>      <h1>Tea shop</h1>      <Suspense fallback={<p>Loading teas…</p>}>        <TeaList teasPromise={teasPromise} />      </Suspense>    </main>  );}
ResultLoading…

Line 4 starts the request once, outside any component, and keeps its promise. In the api.js tab, line 9 answers after 600 ms. Line 7 reads the promise with use. For the first 600 ms, the teas aren't there yet, so the Suspense on line 21 shows Loading teas…. The heading on line 20 is outside it, so it shows at once. When the promise settles, React shows the list.

What changes in Next.js. Two things. First, the server can start the request and hand the promise to a Client Component, which reads it with use. Second, the server streams the page: it sends the HTML around the Suspense first, with the fallback, and the waiting part later in the same response. Here, getTeas in app/teas.ts calls connection() on line 5, which waits for a real request, so the teas are read for each visitor instead of during the build.

The teas, started on the server and read in the browserNext.js
app/
app/page.tsx
import { Suspense } from "react";import { getTeas } from "./teas";import TeaList from "./tea-list"; export default function Home() {  const teasPromise = getTeas();  return (    <main>      <h1>Tea shop</h1>      <Suspense fallback={<p>Loading teas…</p>}>        <TeaList teasPromise={teasPromise} />      </Suspense>    </main>  );}
Recorded from Next.js 16.4.3
localhost:3000/Tea shop
Tea shop
  • Green tea
  • Mint tea
  • Lemon tea

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

Line 6 of app/page.tsx calls getTeas() without await, and line 11 hands the promise to TeaList. Line 8 of app/tea-list.tsx reads it with use, as in the live example. Open the Terminal tab: ◐ / means prerendered as static HTML with dynamic server-streamed content. Here, the heading and the fallback were made during the build, and the teas are streamed in for each request. The Network tab shows the HTML as it arrived: the fallback Loading teas… near the top, and the teas further down, which React swaps in. The Browser tab shows the page after the swap.

Two browser windows for the same page. Left, when the HTML starts to arrive: Tea shop, and the fallback Loading teas…. Right, once the teas are ready on the server: Tea shop, Green tea, Mint tea and Lemon tea. An arrow from left to right says the teas are streamed in.The HTML startspageTea shopLoading teas…streamedThe teas are readypageTea shopGreen teaMint teaLemon teaone response, sent in two parts
The recorded page's two moments. The first part of the HTML holds the heading and the Suspense fallback. When the teas are ready, the server sends them in the same response, and they take the fallback's place.

Suspense, The use API and Streaming teach these in full. In Next.js, a loading.tsx file is a Suspense around a whole page; Loading UI shows it.

Server Components and Server Functions

A Server Component is a React component that runs only on the server. It can be async, wait for data, and read things the browser must never see, like a database password. Its code never goes to the browser; only what it rendered does. A Client Component is one whose file starts with "use client", or is imported by such a file. Its HTML is made on the server too, and then its code runs in the browser. In Next.js, components start as Server Components.

A Server Function is an async function in a file that starts with "use server". It runs on the server, but the browser can call it: as a form's action, or from a Client Component. The browser never gets its code, only a reference to it.

Each row is one thing you might want a component to do, and whether each kind of component can do it:

What each kind of component can do
What you want to doIn a Server ComponentIn a Client Component
Wait for data with awaitYes. It can be an async function.No. It can read a promise with use instead.
Keep state with useStateNo. The build stops with an error.Yes.
Handle a click with onClickNo. A handler is a function, and a function can't reach the browser.Yes.
Run an EffectNo. Effects run only in the browser.Yes, in the browser, after React has put it on the screen.
Read a database or a secret keyYes. Its code never reaches the browser; only what it renders does.No. Its code goes to the browser too.
Have its HTML made on the serverYes.Yes. Then it runs in the browser too.

Try it below. Each button is a version of the page's app/page.tsx, built with real Next.js. Before you click, guess which versions build.

Playground: what can a Server Component do?
The tea shop, with this version of app/page.tsxNext.js
app/
app/page.tsx
import { getTeas } from "./teas";import TeaCard from "./tea-card"; export default async function Home() {  const teas = await getTeas();  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 ...✓ Generating static pages (3/3)  Finalizing page optimization ... Route (app)┌ ○ /└ ○ /_not-found ○  (Static)  prerendered as static content
Try it yourself
Waits for data

Home is an async function, and line 5 waits for getTeas(). A Server Component can do that. The build passes and prints ○ /, prerendered as static content: here, made during the build.

Every version was built with next build from Next.js 16.4.3; the Terminal shows what it printed, and the other tabs what next start sent back. Only app/page.tsx and the files it imports change. Reset goes back to the first version.

The first and last versions build; the middle two don't. A Server Component can wait for data, but it can't keep state. It can hand a Client Component data, but not an ordinary function: a function is code, and a Server Component's code stays on the server. A Server Function is the way through. In the last version, addToCart comes from app/actions.ts, whose line 1 is "use server". Open its Network tab: the page's JavaScript holds the button's code, but not the text addToCart ran on the server from line 4 of app/actions.ts. The function stays on the server, and the button calls it from there.

Props crossing from a Server Component on the left to a Client Component on the right, over a dashed line. The text Mint tea arrives as text, the number 3 as a number, teasPromise as a promise, and the Server Function addToCart as a reference. An ordinary function, name arrow dot dot dot, is crossed out: the build fails.Server ComponentClient Component"Mint tea"arrives as text3arrives as a numberteasPromisearrives as a promiseaddToCartarrives as a reference(name) => …the build fails
Props cross from the server to the browser as data. Text, numbers, plain objects and promises cross as they are. A Server Function crosses as a reference the browser can call. An ordinary function can't cross, and the build stops.

Server Components and Server Functions teach how React itself does this. Next.js has a whole stage on it, Server and Client Components.

Forms and actions

In React 19, a form's action can be a function. When the form is submitted, React calls it with the form's fields as FormData. When the action finishes without an error, React resets the form's uncontrolled fields: the ones without a value prop, like this box. A controlled field, whose value comes from state, keeps what its state says. useActionState keeps what the action returned, and says whether it's still running. Type Fresh in the box and press Save review.

A review form with useActionStatePop out in the code editor (opens in a new tab)JSX
import { useActionState } from "react";import { saveReview } from "./api.js"; export default function App() {  const [message, formAction, isPending] = useActionState(saveReview, "");  return (    <form action={formAction}>      <label>        Your review <input name="review" />      </label>      <button disabled={isPending}>{isPending ? "Saving…" : "Save review"}</button>      {message && <p>{message}</p>}    </form>  );}
ResultLoading…

Line 5 calls useActionState with the action, saveReview, and a first message, "". It gives back three things: the latest message, a formAction to give the form on line 7, and isPending. While saveReview waits (600 ms, in the api.js tab), line 11 shows Saving… on a button that can't be pressed. Then line 12 shows Saved: Fresh, and the box is empty again.

What changes in Next.js. The action can be a Server Function, so saving happens on the server. The form is the same code with "use client" on line 1, because useActionState is a hook, and saveReview now comes from app/actions.ts:

The same form, with a Server Function as its actionNext.js
app/
app/review-form.tsx
"use client"; import { useActionState } from "react";import { saveReview } from "./actions"; export default function ReviewForm() {  const [message, formAction, isPending] = useActionState(saveReview, "");  return (    <form action={formAction}>      <label>        Your review <input name="review" />      </label>      <button disabled={isPending}>{isPending ? "Saving…" : "Save review"}</button>      {message && <p>{message}</p>}    </form>  );}
Recorded from Next.js 16.4.3

GET /200

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
X-Powered-By Next.js
Cache-Control s-maxage=31536000
Content-Type text/html; charset=utf-8

The HTML that arrived, one element per line, with the code of its 16 scripts left out

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="preload" as="script" fetchpriority="low" href="/_next/static/chunks/3cfn99dbrcvvp.js">
    <script src="/_next/static/chunks/2gsxhio73malr.js" async=""></script>
    <script src="/_next/static/chunks/2ki52vdnbo2ft.js" async=""></script>
    <script src="/_next/static/chunks/0ooaq-girjaeh.js" async=""></script>
    <script src="/_next/static/chunks/turbopack-3-ry682tkyiqk.js" async=""></script>
    <script src="/_next/static/chunks/1tq6ppe__m8m-.js" async=""></script>
    <script src="/_next/static/chunks/1v8osnl9wukv9.js" async=""></script>
    <title>Tea shop</title>
    <script src="/_next/static/chunks/0cz1d0mv5g_q7.js" nomodule=""></script>
  </head>
  <body>
    <div hidden="">
      <!--$-->
      <!--/$-->
    </div>
    <!--&-->
    <main>
      <h1>Mint tea</h1>
      <form action="" enctype="multipart/form-data" method="POST"><input type="hidden" name="$ACTION_REF_1"><input type="hidden" name="$ACTION_1:0" value="{&quot;id&quot;:&quot;60bb3a4b6c673488ffcfb081cc3359ac63e6b36614&quot;,&quot;bound&quot;:&quot;$@1&quot;}"><input type="hidden" name="$ACTION_1:1" value="[&quot;&quot;]"><input type="hidden" name="$ACTION_KEY" value="k67721075b722be81e4508015683e29d2"><label>Your review <input name="review"></label><button>Save review</button></form>
    </main>
    <!--$-->
    <!--/$-->
    <!--/&-->
    <script id="_R_">…</script>
    <script src="/_next/static/chunks/3cfn99dbrcvvp.js" async=""></script>
    <script>…</script>
    <script>…</script>
    <script>…</script>
    <script>…</script>
    <script>…</script>
    <script>…</script>
    <script>…</script>
  </body>
</html>

The Network tab shows the form's HTML: <form action="" method="POST">, with hidden fields that hold the action's id. So the form works even before the JavaScript has loaded: the browser posts the fields and the id to the page's own address, and the server runs saveReview. Once React has hydrated the page, it sends the form itself, and isPending shows Saving… as in the live example. The page's JavaScript contains Save review, but not saveReview ran on the server from line 5 of app/actions.ts.

A browser window with the review form: the review Fresh, and a Save review button. An arrow to a server window carries the form's data and the action's id. The server runs saveReview. An arrow back to the browser carries the answer: Saved: Fresh.browserReview: FreshSave reviewfields + action idSaved: FreshserversaveReview(…)runs only on the server
Submitting the review form. The browser sends the form's fields and the Server Function's id. The server runs saveReview, and the answer comes back to useActionState, which shows it.

Forms, Form actions and Optimistic UI teach these in full. The course's data stage builds forms that save with Server Functions.

A tea page that uses all of it

Real pages mix all of these. Here is the Mint tea page: the card from the overview, the reviews, which are read for each visitor, and the review form. Read app/page.tsx, then the other files.

The Mint tea pageNext.js
app/
app/page.tsx
import { Suspense } from "react";import { getTea } from "./data";import TeaCard from "./tea-card";import Reviews from "./reviews";import ReviewForm from "./review-form"; export default async function TeaPage() {  const tea = await getTea("mint");  return (    <main>      <TeaCard name={tea.name} price={tea.price} />      <h2>Reviews</h2>      <Suspense fallback={<p>Loading reviews…</p>}>        <Reviews teaId={tea.id} />      </Suspense>      <ReviewForm />    </main>  );}
Recorded from Next.js 16.4.3
localhost:3000/Tea shop
Mint tea

$3 a cup

Add to cart
Reviews
  • Asha: Fresh and bright.
  • Ravi: Lovely after dinner.
Save review

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

  • Lines 7 and 8 of app/page.tsx: TeaPage is an async Server Component that waits for the tea.
  • Line 11 hands the tea's name and price to TeaCard, the Client Component with the count.
  • Lines 13 to 15 put Reviews in a Suspense. Reviews is an async Server Component too, and line 9 of app/data.ts makes it wait for a request, so the server streams it in.
  • Line 16 adds ReviewForm, a Client Component whose action is the Server Function saveReview.

The Terminal says ◐ /: a static shell, with the reviews streamed in for each request. In the Network tab, the HTML holds Loading reviews… first, and the reviews from Asha and Ravi further down. The page's JavaScript contains Add to cart and Save review, but not the reviews' text. Reviews is a Server Component, so its code, and the code that loads the reviews, stay on the server. What it rendered, the reviews' text, is sent to the browser in the HTML.

Sort these components: which ones can stay Server Components, and which need "use client"?

Server Component or Client Component?
  • TeaPage, which waits for getTea("mint")
  • Reviews, which waits for the reviews inside Suspense
  • TeaCard, whose button counts clicks with useState
  • ReviewForm, which calls useActionState
  • A footer that shows the shop's address
  • A line that shows the window's width, measured in an Effect
  • A list that reads the shop's database with a secret password
  • A Like button that turns red when it's clicked
Try it yourself
0 of 8 correct

Sort each component: can it stay a Server Component, or does its file need "use client"?

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

Common mix-ups

  • "Next.js has its own kind of React." No. It installs React 19.3.0, and pages, layouts and buttons are ordinary React components. Next.js decides where each one runs.
  • "A Client Component only runs in the browser." No. Its HTML is made on the server too, like each card's Add to cart in the recorded HTML. Then its code also runs in the browser.
  • "Every component needs "use client"." No. Only the ones that need state, event handlers, Effects or browser APIs. The rest stay Server Components, and their code stays on the server.
  • "Effects run on the server first." No. Effects run only in the browser. The server's HTML shows the state from before the Effect, like Measuring your window….
  • "A Next.js page loads its data in an Effect." It can, in a Client Component, but it usually doesn't need to: an async Server Component waits for the data on the server, and the data is in the HTML.
  • "A Server Function's code is sent to the browser." No. The browser gets an id it sends back; the code stays on the server.

Each row is one piece of React: how it behaves in this lesson's client-rendered React examples, and what changes in Next.js.

The same React, in this lesson's client-rendered React examples and in Next.js
Piece of ReactIn this lesson's client-rendered React examplesIn Next.js
Components and propsEvery component runs in the browser, because these examples render there.Components start as Server Components. A file with "use client" starts the part that also runs in the browser.
State and event handlersAnywhere.Only in Client Components.
EffectsRun after React puts the component on the screen.The same, in the browser only. The server's HTML shows the state from before the Effect.
Loading dataIn a promise started in the browser, like getTeas() in api.js.In an async Server Component, with await.
SuspenseShows the fallback while a part waits in the browser.The same, and the server streams the waiting part into the HTML when it's ready.
Form actionsA function that runs in the browser.Often a Server Function, which runs on the server. The form works before the JavaScript has loaded.

Practice exercises

The React exercises run in the page: press Edit & run to try your answer. The Next.js ones can't run inside this page.

Exercise 1 · Warm-upCount the clicks

In the three tea cards (TeaCard.jsx is the card from the section above), you click Mint tea's button twice. What does it say now?

import TeaCard from "./TeaCard.jsx";

const teas = [
  { id: "green", name: "Green tea", price: 4 },
  { id: "mint", name: "Mint tea", price: 3 },
  { id: "lemon", name: "Lemon tea", price: 3 },
];

export default function App() {
  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-upFind the Client Components

    These are the Mint tea page's files. Which two start with "use client"?

    Starter codeText
    app/
      actions.ts
      data.ts
      layout.tsx
      page.tsx
      review-form.tsx
      reviews.tsx
      tea-card.tsx

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

      Exercise 3 · PracticeRemove an Effect

      This cart copies its total into state with an Effect. Rewrite it without the Effect. What does the paragraph say after one click on Add lemon tea?

      import { useEffect, useState } from "react"; const prices = { green: 4, mint: 3, lemon: 3 }; export default function App() {  const [cart, setCart] = useState(["green", "mint"]);  const [total, setTotal] = useState(0);   useEffect(() => {    setTotal(cart.reduce((sum, id) => sum + prices[id], 0));  }, [cart]);   return (    <>      <p>        {cart.length} teas, total ${total}      </p>      <button onClick={() => setCart([...cart, "lemon"])}>Add lemon tea</button>    </>  );}
      ResultLoading…

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

        Exercise 4 · PracticeShow a fallback

        This page shows nothing at all for 600 ms, because TeaList waits and there's no Suspense around it. Add one with the fallback Loading teas…. What does the page show during the first 600 ms now?

        import { use } from "react";import { getTeas } from "./api.js"; const teasPromise = getTeas(); function TeaList({ teasPromise }) {  const teas = use(teasPromise);  return (    <ul>      {teas.map((tea) => (        <li key={tea.id}>{tea.name}</li>      ))}    </ul>  );} export default function App() {  return (    <main>      <h1>Tea shop</h1>      <TeaList teasPromise={teasPromise} />    </main>  );}
        ResultLoading…

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

          Exercise 5 · ChallengePredict the build

          This is app/page.tsx, a Server Component. AddButton is a Client Component that calls onAdd on a click. Does next build pass, or fail?

          Starter codeTSX
          import AddButton from "./add-button";
          
          export default function Home() {
            return (
              <main>
                <h1>Tea shop</h1>
                <AddButton name="Mint tea" onAdd={(name) => console.log(name)} />
              </main>
            );
          }
          

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

            Exercise 6 · ChallengeShow the wait

            This review form gives no sign that it's saving. Make its button say Saving… and turn off while saveReview waits. What does the button say during the wait?

            import { useActionState } from "react";import { saveReview } from "./api.js"; export default function App() {  const [message, formAction] = useActionState(saveReview, "");  return (    <form action={formAction}>      <label>        Your review <input name="review" />      </label>      <button>Save review</button>      {message && <p>{message}</p>}    </form>  );}
            ResultLoading…

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

              Check your understanding

              For each question, ask where the code runs: on the server, in the browser, or both.

              The React for Next.js quiz · 7 questionsScore: first tries count
              1. Question 1 of 7In a Next.js app, which components can call useState?

                Choose an answer to see the explanation.

              2. Question 2 of 7The recorded HTML of the window width page says Measuring your window…, not a width. Why?

                Choose an answer to see the explanation.

              3. Question 3 of 7What does this app show during its first 600 ms?

                Read the code, then predictPop out in the code editor (opens in a new tab)JSX
                import { Suspense, use } from "react";
                import { getTeas } from "./api.js";
                
                const teasPromise = getTeas();
                
                function TeaList({ teasPromise }) {
                  const teas = use(teasPromise);
                  return (
                    <ul>
                      {teas.map((tea) => (
                        <li key={tea.id}>{tea.name}</li>
                      ))}
                    </ul>
                  );
                }
                
                export default function App() {
                  return (
                    <main>
                      <h1>Tea shop</h1>
                      <Suspense fallback={<p>Loading teas…</p>}>
                        <TeaList teasPromise={teasPromise} />
                      </Suspense>
                    </main>
                  );
                }

                Choose an answer to see the explanation.

              4. Question 4 of 7Which of these can a Server Component pass to a Client Component as a prop?

                Choose an answer to see the explanation.

              5. Question 5 of 7Where does a Next.js page usually load the data it shows?

                Choose an answer to see the explanation.

              6. Question 6 of 7When the review form's action is a Server Function, what does the browser get for saveReview?

                Choose an answer to see the explanation.

              7. Question 7 of 7In this form, what does the button say while saveReview waits?

                Read the code, then predictPop out in the code editor (opens in a new tab)JSX
                import { useActionState } from "react";
                import { saveReview } from "./api.js";
                
                export default function App() {
                  const [message, formAction, isPending] = useActionState(saveReview, "");
                  return (
                    <form action={formAction}>
                      <label>
                        Your review <input name="review" />
                      </label>
                      <button disabled={isPending}>{isPending ? "Saving…" : "Save review"}</button>
                      {message && <p>{message}</p>}
                    </form>
                  );
                }

                Choose an answer to see the explanation.

              Key takeaways

              • Next.js code is React code: pages, layouts and buttons are components with props, state, Effects and Suspense.
              • Components start as Server Components. They can be async and wait for data, and their code stays on the server.
              • A file that starts with "use client" marks the part that also runs in the browser. State, event handlers and Effects live there.
              • Effects run only in the browser. The server's HTML shows the state from before them.
              • Suspense shows a fallback while a part waits, and the server streams that part in. A Client Component can read a promise from the server with use.
              • A Server Component hands a Client Component data, promises and Server Functions, never ordinary functions. A Server Function runs on the server; the browser only gets a reference to it.
              • A form's action can be a Server Function, and the form then works before the JavaScript has loaded.

              Remember the one-liner.
              Write React as you know it; Next.js runs it on the server, except the parts marked "use client".

              Coming next: How Next.js works: a first look. It follows one visit to the tea shop, from next build to a click on a link, and shows what travels between the server and the browser.

              CompleteFrontend Clear concepts. Working examples.