skip to content

When is the third argument to React's useSyncExternalStore, getServerSnapshot, required, what does it have to return, and what happens if you leave it out?

level: middleimportance: should knowfreq 30%

answer

  1. there is no window on the server
  2. the optional third argument
  3. also used for the first client render
  4. must match what the server produced
  5. omitting it throws during server rendering

basics

~20 s

getServerSnapshot is required whenever the component is server-rendered. React calls it on the server and again for the initial hydration render, so it must return a value available without browser APIs and identical on both sides. Omitting it makes React throw during server rendering.

solid answer

~50 s

`getServerSnapshot` exists because `getSnapshot` usually reads a browser-only source — `window.matchMedia`, `navigator.onLine`, `localStorage` — none of which exist while rendering on the server. React calls `getServerSnapshot` in two places: during the server render, and during the client's initial hydration render. That second call is the one people forget, and it is the whole point: hydration must produce the same output the server produced, so React deliberately uses the server value for the first client render and only switches to `getSnapshot` afterwards. The function must therefore be deterministic — a constant default, or a value serialized into the initial payload — never something read from the DOM or from a browser global. If you server-render a component whose hook has no third argument, React throws an error saying `getServerSnapshot` is missing and is required for server-rendered content. Client-only apps can omit it safely.

go deeper

for a junior

Know that server rendering has no window or navigator, so the hook takes an extra function that supplies a safe default value in that environment.

for a middle

State both call sites — the server render and the client's initial hydration render — and explain that the value must be identical in both so hydration matches.

for a senior

Discuss the consequence: a default that differs from the user's real value produces a post-hydration correction, and you decide between a good default, serializing the value, or deferring that branch.

for a principal

Treat it as part of the store's contract with server rendering: decide which state is knowable at request time versus client-only, and standardise that so every consumer of the store handles the boundary the same way.

## The problem it solves `getSnapshot` is written for the browser. A typical one looks like `() => navigator.onLine` or `() => window.matchMedia('(prefers-color-scheme: dark)').matches`. Run that during server rendering and it throws, because there is no `window` and no `navigator`. Even where a global happens to exist, the server has no idea what the user's viewport or connection looks like. The third parameter is React's answer: a separate reader used wherever the browser value is unavailable or unusable. ```js const isOnline = useSyncExternalStore(subscribe, getSnapshot, () => true); ``` ## Where React calls it Two places, and both matter: 1. **The server render.** Producing the HTML, React has no browser, so it calls `getServerSnapshot`. 2. **The initial hydration render on the client.** The browser *is* available here, and React still calls `getServerSnapshot` rather than `getSnapshot`. The second one is the interesting one. Hydration attaches React to server-produced HTML, and it requires the first client render to produce the same output as the server did. If React used the real browser value on that first pass, a user on a narrow screen would render different markup than the server's default and hydration would mismatch. By using the server value for the initial render and only reading `getSnapshot` on subsequent renders, React keeps hydration consistent and then corrects to the real value immediately afterwards. The practical consequence: a value that differs between the server default and the browser reality produces a visible flash — the correct-but-generic value first, then the real one. That is expected behaviour, not a bug, and the usual mitigations are choosing a sensible default or rendering the browser-dependent part only after the first client render. ## What it is allowed to return The function must be deterministic and free of browser access: - **A constant default.** `() => true` for online status, `() => false` for a media query, `() => 'light'` for a theme with a light default. - **A value the server already knows** — for example, store state serialized into the initial payload and read from a module-level variable on both sides. What it may not do is read the DOM, touch `window` or `document`, use `Date.now()` or randomness, or return a fresh object on every call. The identity rule from `getSnapshot` applies here too: React compares snapshots with `Object.is`, so an allocating `getServerSnapshot` reintroduces the same loop. One more constraint worth stating explicitly: the value returned on the server and the value returned during hydration must be the same. In practice that means the function should not depend on anything that differs between the two environments — which is the same rule as "do not touch browser globals", expressed as an outcome. ```js import { useSyncExternalStore } from 'react'; function subscribe(onStoreChange) { const mql = window.matchMedia('(prefers-color-scheme: dark)'); mql.addEventListener('change', onStoreChange); return () => mql.removeEventListener('change', onStoreChange); } function getSnapshot() { return window.matchMedia('(prefers-color-scheme: dark)').matches; } function getServerSnapshot() { return false; // the server assumes light; the client corrects after hydration } export function usePrefersDark() { return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); } ``` ## What happens when you omit it In a purely client-rendered app, nothing — the parameter is genuinely optional and never called. In a server-rendered app, React throws while rendering the component, reporting that `getServerSnapshot` is missing and required for server-rendered content. It is a hard error rather than a warning, because there is no defensible fallback React could invent on your behalf. This is also why a hook that works fine in a client-only project can break the moment the same component is rendered on a server: the failure is not in your store, it is in the missing third argument. ## What to say in an interview Cover four beats: it exists because `getSnapshot` reads browser-only state; React calls it on the server *and* for the initial hydration render; it must be deterministic and identical across both; omitting it throws during server rendering while client-only apps are unaffected. Adding the hydration-render detail is what shows you understand the constraint rather than having memorised the parameter list.

  • Why does React use getServerSnapshot for the first client render instead of the real browser value?
    Because hydration requires the first client render to match the server HTML. Reading the real browser value on that pass could produce different output than the server's default. React uses the server value once, then reads `getSnapshot` on every render afterwards, so the correct value appears immediately after hydration.
  • The server default and the real browser value differ, and users see a brief flash. What are the options?
    Pick a default that is right for most users so the flash is rare, ship the value from the server when it is actually knowable there, or render the browser-dependent branch only after the first client render so nothing changes under the user. There is no way to know a client-only value during server rendering.
  • Can getServerSnapshot read from a cookie or request-scoped value?
    Only if that value is also available identically during client hydration — for example serialized into the initial payload and read from the same module on both sides. Reading it on the server and defaulting on the client gives two different first renders, which is exactly the mismatch the parameter exists to prevent.

saying these in an interview costs you the question

  • Thinks it only ever runs on the server
  • Reads window or document inside getServerSnapshot
  • Calls the third argument optional even with server rendering
  • Returns a freshly allocated object from it
  • Expects React to silently fall back when it is missing

context