Hydration & partial interactivity
HTML lands first. Then the JS bundle arrives, React walks the DOM, and event listeners get attached. That handover is hydration. Until it finishes, your page is a poster.
Rendered by a Server Component. There's no JavaScript attached to it, clicking, hovering, focusing all work, but nothing happens. This is what every page looks like before hydration.
This button needs JavaScript to do anything. Try it before and after hydration completes.
The hydration timeline
- t=0HTML arrives. The page is visible, text, layout, images, the whole markup tree. But every button is inert.
- t≈1JS bundle downloads.Network and parse time. Pure waiting from the user's perspective.
- t≈2React hydrates. It walks the existing DOM, reconciles it with the component tree, and attaches event listeners.
- t≈2+Interactive.The same button now responds to clicks. The user usually doesn't notice the gap, until the bundle is too big.
Streaming + Suspense
Next.js can flush HTML in pieces. A slow part of the tree can be wrapped in a <Suspense> boundary; the rest of the page renders immediately, and the slow part streams in when ready.
The code
// Server-rendered button, no JS, never interactive
export function StaticButton() {
return <button>Looks like a button, does nothing</button>;
}
// Client island, interactive, but only after hydration
"use client";
import { useEffect, useState } from "react";
export function HydrationDemo() {
const [hydrated, setHydrated] = useState(false);
useEffect(() => { setHydrated(true); }, []);
return (
<button onClick={...}>
{hydrated ? "Hydrated" : "Awaiting JS"}
</button>
);
}Why partial interactivity matters
With React Server Components and "use client" islands, only the interactive parts of the page need JS. Static content stays as plain HTML. The bundle is smaller, hydration is faster, and the user reaches interactivity sooner.