Hydration Proof

Search documentation

Find a page or section

Render a fallback on the server, the real thing after hydration.

A ClientOnly Next.js component shows a fallback on the server and during the first browser render. It shows its children only after hydration, so the two initial renders match. Use it for a map, chart or other UI that needs browser APIs while rendering.

Build a ClientOnly Next.js component

useSyncExternalStore returns the server snapshot during server rendering and hydration. It then switches to the browser snapshot. Use that change to show the fallback first and the children later:

components/client-only.tsx
"use client";
 
import { useSyncExternalStore, type ReactNode } from "react";
 
const subscribe = () => () => {};
 
export function ClientOnly({
  children,
  fallback = null,
}: {
  children: ReactNode;
  fallback?: ReactNode;
}) {
  const isClient = useSyncExternalStore(
    subscribe,
    () => true, // in the browser, after hydration
    () => false // on the server and during hydration
  );
  return isClient ? children : fallback;
}
app/stores/page.tsx
import { ClientOnly } from "@/components/client-only";
import { StoreMap } from "./store-map";
 
export default function Page() {
  return (
    <ClientOnly fallback={<div className="h-96 rounded bg-muted" />}>
      <StoreMap />
    </ClientOnly>
  );
}

You can also start with useState(false) and set it to true in an effect. See two-pass rendering for that approach.

Two things to keep in mind:

  • The child's module still loads on the server. ClientOnly stops the child from rendering there, not from being imported. A library that touches window when it is imported needs next/dynamic with ssr: false instead.
  • Give the fallback the final size. Otherwise the content jumps when the real component replaces it.

How to render components in Next.js only on the client

"use client" alone does not stop server rendering. Next.js still makes HTML for Client Components before hydrating them. To delay a component until the browser is ready, choose one of these approaches:

OptionServer rendersCode is loadedUse it for
ClientOnly wrapperThe fallbackWith the pageComponents that read the browser while rendering
next/dynamic with ssr: falseThe loading fallbackSeparately, in the browserLibraries that need window when imported, large widgets
Two-pass rendering in the componentIts server versionWith the pageOne value that differs, such as a stored preference

ClientOnly Remix and React Router: remix-utils

remix-utils provides ClientOnly for React Router and Remix. Its child is a function that runs in the browser:

app/routes/dashboard.tsx
import { ClientOnly } from "remix-utils/client-only";
import { Chart } from "~/components/chart";
 
export default function Dashboard() {
  return (
    <ClientOnly fallback={<div className="h-64" />}>
      {() => <Chart />}
    </ClientOnly>
  );
}

The fallback stays in place through the first browser render. Afterwards, the component shows its child. For smaller differences, use the package's useHydrated hook directly. See Remix hydration errors.

ClientOnly TanStack Router and Start

TanStack Router also exports ClientOnly. Pass a fallback for the server and first browser render:

src/routes/dashboard.tsx
import { ClientOnly, createFileRoute } from "@tanstack/react-router";
import { Charts, FallbackCharts } from "~/components/charts";
 
export const Route = createFileRoute("/dashboard")({
  component: Dashboard,
});
 
function Dashboard() {
  return (
    <ClientOnly fallback={<FallbackCharts />}>
      <Charts />
    </ClientOnly>
  );
}

See TanStack Start for testing those routes.

Can you disable hydration or only partially hydrate a Next.js app?

You cannot keep a Client Component's server HTML and turn off hydration for that component. You can decide which parts of the page need client code:

  • Keep static parts as Server Components. Their code never runs in the browser, and nothing about them needs hydrating except the plain HTML elements. This is how the App Router lets you only partially hydrate a Next.js app.
  • Mark only the interactive leaves "use client". A button or a form hydrates; the article around it does not ship its code.
  • Render browser-only parts on the client with a ClientOnly wrapper or next/dynamic with ssr: false.
  • Split the page with Suspense boundaries. Each boundary hydrates on its own, and a mismatch inside one re-renders only that boundary.

What client-only rendering costs

  • No HTML for that part. Crawlers, link previews and users without JavaScript see the fallback.
  • It appears later. The content waits for hydration, or for its own chunk to load.
  • Layout shift, unless the fallback has the size of the final content.

Run hydration-proof to check the result. A properly delayed component has no hydration finding. If another part still renders differently, the report shows the element and both values.

npx hydration-proof test