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, 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:

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

How to fix it

  1. Find the hook. The component stack under the error names the component that called it.

  2. Add a getServerSnapshot that 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
      );
    }
  3. Keep getServerSnapshot free of browser values. It runs in the browser during hydration too. () => localStorage.getItem("theme") as the third argument crashes on the server, and a typeof window check 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-proof

hydration-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