The boundaryserver & client components

I’ll put “use client” on it, to be safe.

Safer than what? We reached for it the same way, for about a year, before anyone made us say what it was protecting against. Every component in the App Router already runs on the server. "use client" is not a precaution, it’s a purchase — for that file and everything it imports. You buy useState, useEffect and onClick. You pay with the database call you can no longer make from here, the API key you can no longer read, and however much React your visitor downloads on their phone.

Watch the two cards below. One rendered in Node.js and arrived as finished HTML — done before you got here. The other arrived as JavaScript and woke up in your tab — the waking is called hydration — and its button is waiting for a click. Only one of them is running Node, and it prints the version to prove it.

live
serverserver-card.tsx

Rendered in Node.js v24.18.0

at 27 Aug 2026, 04:34:00 UTC, then cached. Refresh the page — this card doesn’t re-render, it gets re-served.

  • can query databases, read secrets
  • ships 0 kB of JavaScript
  • cannot use state or onClick
clientclient-card.tsx

0

This button works because this component hydrated in your browser.

  • can use state, effects, onClick
  • ships JavaScript (that’s the deal)
  • cannot read secrets — it lives in your tab

props cross the boundary as serialized data — the import graph decides which side a component runs on.

server-card.tsx + client-card.tsx
// server-card.tsx
import { cacheLife } from "next/cache";

// No "use client" at the top: this is a Server Component.
// It runs in Node.js, its output is cached, zero JS ships.
async function getServerFacts() {
  "use cache";
  cacheLife("hours");
  return {
    nodeVersion: process.version, // browsers don't have one
    renderedAt: new Date().toISOString(),
  };
}

export async function ServerCard() {
  const facts = await getServerFacts();
  return <Card side="server">Rendered in Node.js {facts.nodeVersion}</Card>;
}

// client-card.tsx
"use client"; // this one directive is the entire boundary

import { useState } from "react";

export function ClientCard() {
  const [count, setCount] = useState(0);

  return (
    <Card side="client">
      <button onClick={() => setCount(count + 1)}>Click me</button>
      {count}
    </Card>
  );
}