facebook/react · error · Error

407

407

Error message

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

What it means

When a component using useSyncExternalStore mounts, React reads a snapshot. If that mount render is hydrating server-rendered HTML (getIsHydrating() true) and no third getServerSnapshot argument was supplied, React cannot reproduce the value the server rendered from, so it throws instead of silently mismatching hydration.

Source

Thrown at packages/react-reconciler/src/ReactFiberHooks.js:1660

    queue.lastRenderedState = newState;
  }
  return [newState, dispatch];
}

function mountSyncExternalStore<T>(
  subscribe: (() => void) => () => void,
  getSnapshot: () => T,
  getServerSnapshot?: () => T,
): T {
  const fiber = currentlyRenderingFiber;
  const hook = mountWorkInProgressHook();

  let nextSnapshot;
  const isHydrating = getIsHydrating();
  if (isHydrating) {
    if (getServerSnapshot === undefined) {
      throw new Error(
        'Missing getServerSnapshot, which is required for ' +
          'server-rendered content. Will revert to client rendering.',
      );
    }
    nextSnapshot = getServerSnapshot();
    if (__DEV__) {
      if (!didWarnUncachedGetSnapshot) {
        if (nextSnapshot !== getServerSnapshot()) {
          console.error(
            'The result of getServerSnapshot should be cached to avoid an infinite loop',
          );
          didWarnUncachedGetSnapshot = true;
        }
      }
    }
  } else {
    nextSnapshot = getSnapshot();
    if (__DEV__) {

View on GitHub (pinned to eafeac097b)

Solutions

  1. Pass a third getServerSnapshot argument returning the stable server-side value, e.g. useSyncExternalStore(subscribe, () => window.innerWidth, () => 0)
  2. Cache the server snapshot (repeat calls must return the same value, or you get the separate uncached-getServerSnapshot warning)
  3. For truly client-only UI, gate rendering behind a mounted flag (useEffect sets state) so hydration never sees the store value
  4. If the store can report its initial value synchronously, reuse getSnapshot as getServerSnapshot only when it is safe to call on the server

Example fix

// before
const scrollY = useSyncExternalStore(
  subscribeScroll,
  () => window.scrollY, // throws during hydration: no server snapshot
);

// after
const scrollY = useSyncExternalStore(
  subscribeScroll,
  () => window.scrollY,
  () => 0, // stable server-rendered value
);
Defensive patterns

Strategy: validation

Validate before calling

// Wrapper that refuses the 2-argument form in any SSR-capable app
export function useSyncExternalStoreSSR<T>(
  subscribe: () => () => void,
  getSnapshot: () => T,
  getServerSnapshot: () => T,
): T {
  if (typeof getServerSnapshot !== 'function') {
    throw new Error('getServerSnapshot is required when hydration is possible');
  }
  return React.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
}

Type guard

// Type that makes the server snapshot non-optional at every call site
type SSRStoreHook = <T>(
  subscribe: () => () => void,
  getSnapshot: () => T,
  getServerSnapshot: () => T, // required, not `?:`
) => T;

Prevention

When it happens

Trigger: useSyncExternalStore(subscribe, getSnapshot) called with only two arguments during hydration — i.e. the first client render over SSR HTML (Next.js, Remix, react-dom/hydrateRoot).

Common situations: Adding a browser-only store subscription (window size, matchMedia, localStorage, third-party stores) to a component that is server-rendered; hydration failures after introducing the hook in an SSR framework; libraries integrating external stores without a server snapshot.

Related errors


AI-assisted analysis of facebook/react@eafeac097b (2026-08-21). Data as JSON: /api/errors/d590ba7af02c1d1b. Report an issue: GitHub.