Pebellslearn by building
0%
RoadmapsMatrixCoursesSandboxSign in
The App Router  ›  Capstone Project

Client Components

Rendering Pages 15 minAdding 'use client' at the top of a file makes it and everything it imports run in the browser too, which you need for state, effects, event handlers and browser APIs.

Client Components

The trail page is a server component, but Trailhead also needs a Save button: tap the heart, it fills in, and the choice is remembered in localStorage. That needs state, a click handler and a browser API, none of which exist on the server. So this one piece has to cross over to the client.

The rule

"use client" at the top opens a door; everything imported through it goes to the browser.

Put the directive as the very first line of a file, before the imports:

// app/trails/[slug]/save-button.tsx
"use client";
import { useState, useEffect } from "react";

export function SaveButton({ slug }: { slug: string }) {
  const [saved, setSaved] = useState(false);
  useEffect(() => {
    setSaved(localStorage.getItem(`saved:${slug}`) === "1");
  }, [slug]);
  function toggle() {
    const next = !saved;
    setSaved(next);
    localStorage.setItem(`saved:${slug}`, next ? "1" : "0");
  }
  return <button onClick={toggle}>{saved ? "♥ Saved" : "♡ Save"}</button>;
}

The server page simply renders it:

<SaveButton slug={trail.slug} />

Three facts to keep straight:

  • It is a boundary, not a label. The file with "use client" and every module it imports become part of the browser bundle. You do not need the directive on each child.
  • Client components are still prerendered. Next.js renders them to HTML on the server for the first load, then hydrates them in the browser. That is why localStorage is read inside useEffect, which only runs in the browser.
  • Props must be serialisable when a server component passes them to a client component: strings, numbers, plain objects, arrays, dates. Not ordinary functions or class instances.

A server component can also be passed as children to a client component. It is rendered on the server and slotted in, so a client wrapper does not drag its contents into the bundle.

The shortcut

Push the door down to the leaves. Mark the smallest component that needs interactivity, not the page around it. The higher the directive, the more code ships.

Placing the boundary on the trail pageStep 1 of 4
  1. Start with everything on the server

    The page, header, notes and photos render on the server.

The trap is putting "use client" on page.tsx to make one button work. The whole page, and everything it imports, then ships to the browser, and it can no longer be an async component that queries the database.

Drills

Answer fast — trust the rule.

quick drill

Where must "use client" go?

quick drill

A client component imports a Stars component without the directive. Where does Stars run?

quick drill

Is a client component's HTML included in the first server response?

quick drill

Which prop can a server page pass to a client component?

quick drill

Only a filter dropdown on /trails needs state. Where should "use client" go?

Remember

  • "use client" on the first line opens the door; that file and its imports ship to the browser.
  • Shortcut: push the door down to the smallest interactive component.
  • Trap: client components still prerender on the server, and props across the door must be serialisable.

Practice — level up

0/1 solved

Write your answer, then compare it with a rubric and a model answer.

ExplainDraw the boundary

Trailhead's trail page shows the trail details from the database and a Save button that toggles a heart and remembers the choice in localStorage. Explain how you split this into components, where 'use client' goes, and how the two parts connect.