From 26e8e0a5a246c62b1036c3191ca44815dc554975 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 18 Oct 2025 22:03:31 +0000 Subject: [PATCH] Add hydration errors guide for React and Solid Co-authored-by: tannerlinsley --- docs/start/config.json | 8 ++ .../framework/react/guide/hydration-errors.md | 122 ++++++++++++++++++ .../framework/solid/guide/hydration-errors.md | 114 ++++++++++++++++ 3 files changed, 244 insertions(+) create mode 100644 docs/start/framework/react/guide/hydration-errors.md create mode 100644 docs/start/framework/solid/guide/hydration-errors.md diff --git a/docs/start/config.json b/docs/start/config.json index cdf9f0b088d..28c7d5030ed 100644 --- a/docs/start/config.json +++ b/docs/start/config.json @@ -101,6 +101,10 @@ "label": "Server Routes", "to": "framework/react/guide/server-routes" }, + { + "label": "Hydration Errors", + "to": "framework/react/guide/hydration-errors" + }, { "label": "Selective SSR", "to": "framework/react/guide/selective-ssr" @@ -190,6 +194,10 @@ "label": "Server Routes", "to": "framework/solid/guide/server-routes" }, + { + "label": "Hydration Errors", + "to": "framework/solid/guide/hydration-errors" + }, { "label": "Selective SSR", "to": "framework/solid/guide/selective-ssr" diff --git a/docs/start/framework/react/guide/hydration-errors.md b/docs/start/framework/react/guide/hydration-errors.md new file mode 100644 index 00000000000..54929b41ebd --- /dev/null +++ b/docs/start/framework/react/guide/hydration-errors.md @@ -0,0 +1,122 @@ +--- +id: hydration-errors +title: Hydration Errors +--- + +### Why it happens +- **Mismatch**: Server HTML differs from client render during hydration +- **Common causes**: `Intl` (locale/time zone), `Date.now()`, random IDs, responsive-only logic, feature flags, user prefs + +### Strategy 1 — Make server and client match +- **Pick a deterministic locale/time zone on the server** and use the same on the client +- **Source of truth**: cookie (preferred) or `Accept-Language` header +- **Compute once on the server** and hydrate as initial state + +```tsx +// src/start.ts +import { createStart, createMiddleware } from '@tanstack/react-start' +import { getRequestHeader, getCookie, setCookie } from '@tanstack/react-start/server' + +const localeTzMiddleware = createMiddleware().server(async ({ next }) => { + const header = getRequestHeader('accept-language') + const headerLocale = header?.split(',')[0] || 'en-US' + const cookieLocale = getCookie('locale') + const cookieTz = getCookie('tz') // set by client later (see Strategy 2) + + const locale = cookieLocale || headerLocale + const timeZone = cookieTz || 'UTC' // deterministic until client sends tz + + // Persist locale for subsequent requests (optional) + setCookie('locale', locale, { path: '/', maxAge: 60 * 60 * 24 * 365 }) + + return next({ context: { locale, timeZone } }) +}) + +export const startInstance = createStart(() => ({ + requestMiddleware: [localeTzMiddleware], +})) +``` + +```tsx +// src/routes/index.tsx (example) +import * as React from 'react' +import { createFileRoute } from '@tanstack/react-router' +import { createServerFn } from '@tanstack/react-start' +import { getCookie } from '@tanstack/react-start/server' + +export const getServerNow = createServerFn().handler(async () => { + const locale = getCookie('locale') || 'en-US' + const timeZone = getCookie('tz') || 'UTC' + return new Intl.DateTimeFormat(locale, { dateStyle: 'medium', timeStyle: 'short', timeZone }).format(new Date()) +}) + +export const Route = createFileRoute('/')({ + loader: () => getServerNow(), + component: () => { + const serverNow = Route.useLoaderData() as string + return + }, +}) +``` + +### Strategy 2 — Let the client tell you its environment +- On first visit, set a cookie with the client time zone; SSR uses `UTC` until then +- Do this without risking mismatches + +```tsx +import * as React from 'react' +import { ClientOnly } from '@tanstack/react-router' + +function SetTimeZoneCookie() { + React.useEffect(() => { + const tz = Intl.DateTimeFormat().resolvedOptions().timeZone + document.cookie = `tz=${tz}; path=/; max-age=31536000` + }, []) + return null +} + +export function AppBoot() { + return ( + + + + ) +} +``` + +### Strategy 3 — Make it client-only +- Wrap unstable UI in `` to avoid SSR and mismatches + +```tsx +import { ClientOnly } from '@tanstack/react-router' + +—}> + + +``` + +### Strategy 4 — Disable or limit SSR for the route +- Use Selective SSR to avoid rendering the component on the server + +```tsx +export const Route = createFileRoute('/unstable')({ + ssr: 'data-only', // or false + component: () => , +}) +``` + +### Strategy 5 — Last resort suppression +- For small, known-different nodes, you can use React’s `suppressHydrationWarning` + +```tsx + +``` + +### Checklist +- **Deterministic inputs**: locale, time zone, feature flags +- **Prefer cookies** for client context; fallback to `Accept-Language` +- **Use ``** for inherently dynamic UI +- **Use Selective SSR** when server HTML cannot be stable +- **Avoid blind suppression**; use `suppressHydrationWarning` sparingly + +See also: [Execution Model](../execution-model.md), [Code Execution Patterns](../code-execution-patterns.md), [Selective SSR](../selective-ssr.md), [Server Functions](../server-functions.md) diff --git a/docs/start/framework/solid/guide/hydration-errors.md b/docs/start/framework/solid/guide/hydration-errors.md new file mode 100644 index 00000000000..9b2ddd8caa3 --- /dev/null +++ b/docs/start/framework/solid/guide/hydration-errors.md @@ -0,0 +1,114 @@ +--- +id: hydration-errors +title: Hydration Errors +--- + +### Why it happens +- **Mismatch**: Server HTML differs from client render during hydration +- **Common causes**: `Intl` (locale/time zone), `Date.now()`, random IDs, responsive-only logic, feature flags, user prefs + +### Strategy 1 — Make server and client match +- **Pick a deterministic locale/time zone on the server** and use the same on the client +- **Source of truth**: cookie (preferred) or `Accept-Language` header +- **Compute once on the server** and hydrate as initial state + +```tsx +// src/start.ts +import { createStart, createMiddleware } from '@tanstack/solid-start' +import { getRequestHeader, getCookie, setCookie } from '@tanstack/solid-start/server' + +const localeTzMiddleware = createMiddleware().server(async ({ next }) => { + const header = getRequestHeader('accept-language') + const headerLocale = header?.split(',')[0] || 'en-US' + const cookieLocale = getCookie('locale') + const cookieTz = getCookie('tz') // set by client later (see Strategy 2) + + const locale = cookieLocale || headerLocale + const timeZone = cookieTz || 'UTC' + + setCookie('locale', locale, { path: '/', maxAge: 60 * 60 * 24 * 365 }) + + return next({ context: { locale, timeZone } }) +}) + +export const startInstance = createStart(() => ({ + requestMiddleware: [localeTzMiddleware], +})) +``` + +```tsx +// src/routes/index.tsx (example) +import { createFileRoute } from '@tanstack/solid-router' +import { createServerFn } from '@tanstack/solid-start' +import { getCookie } from '@tanstack/solid-start/server' + +export const getServerNow = createServerFn().handler(async () => { + const locale = getCookie('locale') || 'en-US' + const timeZone = getCookie('tz') || 'UTC' + return new Intl.DateTimeFormat(locale, { dateStyle: 'medium', timeStyle: 'short', timeZone }).format(new Date()) +}) + +export const Route = createFileRoute('/')({ + loader: () => getServerNow(), + component: () => { + const serverNow = Route.useLoaderData() as string + return + }, +}) +``` + +### Strategy 2 — Let the client tell you its environment +- On first visit, set a cookie with the client time zone; SSR uses `UTC` until then + +```tsx +import { ClientOnly } from '@tanstack/solid-router' + +function SetTimeZoneCookie() { + if (typeof window !== 'undefined') { + const tz = Intl.DateTimeFormat().resolvedOptions().timeZone + document.cookie = `tz=${tz}; path=/; max-age=31536000` + } + return null +} + + + + +``` + +### Strategy 3 — Make it client-only +- Use `` or Solid’s `` to avoid SSR/hydration + +```tsx +import { ClientOnly } from '@tanstack/solid-router' +import { NoHydration } from 'solid-js/web' + +—}> + + + + + + +``` + +### Strategy 4 — Disable or limit SSR for the route +- Use Selective SSR to avoid rendering the component on the server + +```tsx +export const Route = createFileRoute('/unstable')({ + ssr: 'data-only', // or false + component: () => , +}) +``` + +### Strategy 5 — Last resort suppression +- Prefer the tools above; avoid mismatches instead of hiding them + +### Checklist +- **Deterministic inputs**: locale, time zone, feature flags +- **Prefer cookies** for client context; fallback to `Accept-Language` +- **Use ``/``** for inherently dynamic UI +- **Use Selective SSR** when server HTML cannot be stable + +See also: [Execution Model](../execution-model.md), [Code Execution Patterns](../code-execution-patterns.md), [Selective SSR](../selective-ssr.md), [Server Functions](../server-functions.md)