Smallest client leaf
Keep content on the server; put "use client" on the smallest leaf.
From nextjs-pagespeed.
Move the boundary
Click a component to put "use client" there.
Landing page tree"use client" in page.tsx
Component JS
0 KB
Hydration work
0 ms
Hydrated
0 of 0
Client at the topBad
// app/page.tsx
"use client";
import { useState } from "react";
export default function Page() {
const [added, setAdded] = useState(false);
return (
<>
<Header />
<Hero />
<Features />
<Pricing added={added} onBuy={() => setAdded(true)} />
<FAQ />
<Footer />
</>
);
}
Client leaf onlyGood
// app/page.tsx: Server Component, no directive
export default function Page() {
return (
<>
<Header /> <Hero /> <Features />
<Pricing /> {/* PlanCard renders <BuyButton /> */}
<FAQ /> <Footer />
</>
);
}
// app/buy-button.tsx
"use client";
import { useState } from "react";
export function BuyButton({ plan }: { plan: string }) {
const [added, setAdded] = useState(false);
return (
<button type="button" onClick={() => setAdded(true)}>
{added ? "Added" : `Buy ${plan}`}
</button>
);
}
Click the moment it appears
Press Load, then tap each Add button immediately.
A · Client pageBad
180 KB · 150 ms × CPUB · Client buttonGood
4 KB · 8 ms × CPUWhy it matters, pros and cons
Why it matters
Everything under the directive ships JS and hydrates; taps wait meanwhile.
Pros
- Static sections send HTML, no JS.
- Shorter hydration, better INP.
- Heavy render-only libraries stay on the server.
Cons
- Props crossing the boundary must be serializable.
- Stateful wrappers stay client; pass server content as
children. - Fully interactive apps gain little from finer splits.