Hydration Proof

Search documentation

Find a page or section

Missing getServerSnapshot, which is required for server-rendered content

React error #407: useSyncExternalStore needs a server value.

"Missing getServerSnapshot" means useSyncExternalStore has no server snapshot function. React needs that third argument for server rendering and hydration. Add a function that returns the same initial value on the server and in the browser.

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 reads a value outside React. The snapshot functions serve different stages:

WhenReact calls
Server renderinggetServerSnapshot
Hydration, in the browsergetServerSnapshot again, and expects the server's value
After hydrationgetSnapshot, and re-renders if the value changed

Without getServerSnapshot, React cannot safely render the store's value on the server. It may fall back to client rendering at the nearest Suspense boundary or fail the server render if there is no suitable boundary.

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 useSyncExternalStore without a server snapshot, in an older version or a custom wrapper.

How to fix it

  1. Find the hook in the component stack.

  2. Add a server snapshot for the value shown before the browser store is available. 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
      );
    }
  3. Keep the server snapshot stable. It also runs in the browser during hydration. Do not read localStorage there or branch on typeof window. Return a constant or a value passed from the server.

For a saved preference, a server-readable cookie may provide the initial value. See storage.

Find every instance

require-stable-server-snapshot flags a missing or unstable server snapshot:

npm install -D eslint-plugin-hydration-proof

At runtime, hydration-proof can report a server boundary failure as HP2006, or a fallback during hydration as HP2003 or HP2004:

npx hydration-proof test