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 × CPU
    B · Client buttonGood
    4 KB · 8 ms × CPU

    Why 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.