"Missing getServerSnapshot, which is required for server-rendered content"
means a component called useSyncExternalStore during server rendering or
hydration without its third argument. The server has no browser store to
read, so React needs a value to use instead. Pass a getServerSnapshot
function that returns the same value on the server and during hydration.
The error
React 18 and 19, in development:
Missing getServerSnapshot, which is required for server-rendered content. Will revert to client rendering.In production it is error #407:
Minified React error #407; visit https://react.dev/errors/407 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.What missing getServerSnapshot means
useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) reads a
value from a store outside React, such as navigator.onLine, the window size
or localStorage. React calls the three functions at different times:
| When | React calls |
|---|---|
| Server rendering | getServerSnapshot |
| Hydration, in the browser | getServerSnapshot again, and expects the server's value |
| After hydration | getSnapshot, and re-renders if the value changed |
Without the third function, React cannot render the component on the server
at all. It throws this error, and "will revert to client rendering" means the
nearest <Suspense> boundary is sent without its content and rendered in the
browser instead. With no boundary above it, the server render fails.
Common causes
- A hook written for client-only React, then used in a server-rendered app.
- A copied example that shows only two arguments.
- A store library whose hook calls
useSyncExternalStorewithout a server snapshot, in an older version or a custom wrapper.
How to fix it
-
Find the hook. The component stack under the error names the component that called it.
-
Add a
getServerSnapshotthat returns what the page should show before the browser value is known. Before:hooks/use-online.ts import { useSyncExternalStore } from "react"; function subscribe(callback: () => void) { window.addEventListener("online", callback); window.addEventListener("offline", callback); return () => { window.removeEventListener("online", callback); window.removeEventListener("offline", callback); }; } export function useOnline() { return useSyncExternalStore(subscribe, () => navigator.onLine); }After:
hooks/use-online.ts import { useSyncExternalStore } from "react"; function subscribe(callback: () => void) { window.addEventListener("online", callback); window.addEventListener("offline", callback); return () => { window.removeEventListener("online", callback); window.removeEventListener("offline", callback); }; } export function useOnline() { return useSyncExternalStore( subscribe, () => navigator.onLine, () => true ); } -
Keep
getServerSnapshotfree of browser values. It runs in the browser during hydration too.() => localStorage.getItem("theme")as the third argument crashes on the server, and atypeof windowcheck inside it returns one value on the server and another during hydration, which is a hydration mismatch. Return a constant, or a value the server passed down as a prop.
For values such as the theme, the server can often know the real value from a cookie. See storage in server-rendered apps.
Find every instance
require-stable-server-snapshot
reports useSyncExternalStore calls without a third argument, and browser
reads, the clock and typeof window checks inside getServerSnapshot:
npm install -D eslint-plugin-hydration-proofhydration-proof reports the fallback at runtime: HP2006 when the server could not render the boundary, and HP2003 or HP2004 when React fell back while hydrating, each with React's message attached:
npx hydration-proof test