Hydration Proof

Search documentation

Find a page or section

TanStack Start hydration errors

Deterministic inputs first, client-only rendering second.

A TanStack Start hydration error means the first browser render differs from the server HTML. Start by checking dates, locale and timezone formatting, random ids, responsive logic, and saved preferences. TanStack's hydration guide covers these causes too.

Common hydration errors in TanStack Start

TanStack Start shows React's hydration messages. With React 19, you may see:

Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client.
A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up.

A TanStack Start hydration mismatch usually fits one of these cases:

CauseTypical codeFix
Locale and time zoneIntl.DateTimeFormat(), toLocaleString() without an explicit locale or timeZoneLocale, timezone
The clockDate.now(), new Date() in a componentTime
Random valuesrandom ids, Math.random() in renderRandom values
Screen sizeresponsive logic that reads window or matchMediaMedia queries
User preferencestheme or flags read from the browserTheme, browser APIs

How to fix a TanStack Start hydration error

Choose the fix that preserves server-rendered content when possible. Use client-only rendering when the component truly needs the browser.

Make the server and the client agree

Choose a locale and timezone on the server and pass them to the client. A cookie can store a known preference; Accept-Language can be a fallback for locale. If the server does not know the browser's timezone yet, use a stable timezone such as UTC for the first render.

Let the client report its environment

On a first visit, read the browser's timezone after hydration and save it in a cookie. The server can use it on later requests.

Render unstable UI on the client only

Use ClientOnly from @tanstack/react-router for content that cannot render on the server. It shows a fallback until hydration finishes:

src/components/last-seen.tsx
import { ClientOnly } from "@tanstack/react-router";
import { RelativeTime } from "./relative-time";
 
export function LastSeen({ ts }: { ts: number }) {
  return (
    <ClientOnly fallback={<span>—</span>}>
      <RelativeTime ts={ts} />
    </ClientOnly>
  );
}

Turn off server rendering for a route

TanStack Start can server-render only a route's data with ssr: "data-only", or skip server rendering for that route with ssr: false:

src/routes/unstable.tsx
import { createFileRoute } from "@tanstack/react-router";
import { ExpensiveViz } from "../components/expensive-viz";
 
export const Route = createFileRoute("/unstable")({
  ssr: "data-only",
  component: ExpensiveViz,
});

Suppress the warning as a last resort

If one element must differ, suppressHydrationWarning can hide its warning. It does not fix the difference. See when it is safe.

Test every route with hydration-proof

There is no built-in TanStack Start adapter. Provide the app URL and routes, or configure the build and start commands. You can also write an adapter.

Test an app you started

Start the app, then point hydration-proof at its URL and name the routes:

npx hydration-proof test --url http://localhost:3000 --route / --route /posts

With --url, hydration-proof does not build or start the app. Add --sitemap to include sitemap pages, or --crawl to follow links.

Let hydration-proof build and start it

Use adapter: "none" and give it your commands:

hydration-proof.config.ts
import { defineConfig } from "hydration-proof";
 
export default defineConfig({
  adapter: "none",
  server: {
    build: "npm run build",
    command: "npm run start", // must listen on process.env.PORT
  },
  routes: { paths: ["/", "/posts", "/posts/1"] },
});

hydration-proof picks a free port and passes it as PORT. It also replaces {port} in a command. Without an adapter, server.build runs on every check. Set server.buildWhen: "never" if CI already built the app.

Run npx hydration-proof doctor to see what adapter: "auto" detects. A generic Node adapter may use your build, start, and dev scripts, but you still need to provide routes.

Write an adapter

If you want automatic route discovery, write an adapter with defineAdapter. See writing an adapter for the required pieces.