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
localStorageis read insideuseEffect, 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.
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.
Where must "use client" go?
A client component imports a Stars component without the directive. Where does Stars run?
Is a client component's HTML included in the first server response?
Which prop can a server page pass to a client component?
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.
