# Missing getServerSnapshot, which is required for server-rendered content

> Missing getServerSnapshot means useSyncExternalStore ran on the server without its third argument. Add one that returns the same value on both sides.

Source: https://hydration.jscrate.dev/docs/errors/missing-getserversnapshot
Last updated: 2026-09-18

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

```text
Missing getServerSnapshot, which is required for server-rendered content. Will revert to client rendering.
```

In production it is error #407:

```text
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 `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:

   ```ts title="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:

   ```ts title="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](https://hydration.jscrate.dev/docs/causes/storage).

## Find every instance

[`require-stable-server-snapshot`](https://hydration.jscrate.dev/docs/rules/require-stable-server-snapshot)
reports `useSyncExternalStore` calls without a third argument, and browser
reads, the clock and `typeof window` checks inside `getServerSnapshot`:

```bash
npm install -D eslint-plugin-hydration-proof
```

hydration-proof reports the fallback at runtime:
[HP2006](https://hydration.jscrate.dev/docs/issues/hp2006) when the server could not render the boundary,
and [HP2003](https://hydration.jscrate.dev/docs/issues/hp2003) or [HP2004](https://hydration.jscrate.dev/docs/issues/hp2004) when React
fell back while hydrating, each with React's message attached:

```bash
npx hydration-proof test
```

## Related

- [The require-stable-server-snapshot rule](https://hydration.jscrate.dev/docs/rules/require-stable-server-snapshot)
- [useSyncExternalStore with localStorage](https://hydration.jscrate.dev/docs/causes/storage)
- [Browser-only APIs during render](https://hydration.jscrate.dev/docs/causes/browser-api)
- [There was an error while hydrating](https://hydration.jscrate.dev/docs/errors/there-was-an-error-while-hydrating)
- [Every minified React hydration code](https://hydration.jscrate.dev/docs/errors/minified-react-error-codes)
