Understanding server-side rendering and hydration with React and Zustand
Server-side Rendering (SSR) helps render components into HTML strings on the server, send them to the browser, and “hydrate” the static markup into a fully interactive app on the client.
Hydration turns the initial HTML snapshot from the server into a fully interactive app. The React tree you pass to hydrateRootmust produce the same output as it did on the server.
The React tree you pass to hydrateRoot needs to produce the same output as it did on the server.
The most common causes of hydration errors:
Extra Whitespace
Extra whitespace (like newlines) around the React-generated HTML inside the root node.
// Bad - extra whitespace<div> <App /></div>// Good<div><App /></div>
Browser-Only Checks
Using checks like typeof window !== 'undefined' in your rendering logic.
// Bad - different output on server and clientfunction Component() { const isBrowser = typeof window !== 'undefined' return <div>{isBrowser ? 'Client' : 'Server'}</div>}// Good - use useEffect for client-only codefunction Component() { const [mounted, setMounted] = useState(false) useEffect(() => setMounted(true), []) return <div>{mounted ? 'Client' : 'Server'}</div>}
Browser-Only APIs
Using browser-only APIs like window.matchMedia in your rendering logic.
// Bad - window is not available on serverfunction Component() { const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches return <div>{isDark ? 'Dark' : 'Light'}</div>}// Good - use useEffectfunction Component() { const [isDark, setIsDark] = useState(false) useEffect(() => { setIsDark(window.matchMedia('(prefers-color-scheme: dark)').matches) }, []) return <div>{isDark ? 'Dark' : 'Light'}</div>}
Different Data
Rendering different data on the server and client.
// Bad - Math.random() gives different valuesfunction Component() { return <div>{Math.random()}</div>}// Good - use the same data sourcefunction Component({ randomValue }) { return <div>{randomValue}</div>}