Hydration Proof

Search documentation

Find a page or section

Disallow reading browser-only globals such as window and document while a component renders.

If you read window in render, React runs browser-only code on the server, where window, document and navigator do not exist. The server may crash or produce different HTML. no-browser-global-in-render flags these reads. Read browser values after hydration instead.

Rulehydration-proof/no-browser-global-in-render
What it reportsDisallow reading browser-only globals such as window and document while a component renders
recommended / nextError
strictError
Server ComponentsSkipped with the next preset (they never hydrate)
SuggestionsNo
Optionsnone

What it reports

Reads in render code of window, self, document, navigator, location, history, screen, innerWidth, innerHeight, outerWidth, outerHeight, devicePixelRatio, visualViewport, scrollX, scrollY, pageXOffset and pageYOffset, directly or through window., self. or globalThis. (globalThis.window.innerWidth). Any other property read through window or self (window.__STATE__) is reported too.

The rule checks which variable a name refers to. A prop or local variable named window is not the browser global, so it is not reported. Other rules handle Date, Math, Intl, crypto and performance.

Other rules handle these cases:

Why you can't read window in render (React SSR)

These globals do not exist on the server. The code either throws (ReferenceError: window is not defined, see window is not defined) or, behind a check, renders something other than the browser does:

server HTML:   <aside class="sidebar">        (no window: server rendering failed and fell back, or used a default)
client render: <aside class="sidebar wide">   (window.innerWidth is 1440)

Incorrect

function Sidebar() {
  const wide = window.innerWidth > 1024;
  return <aside className={wide ? "sidebar wide" : "sidebar"} />;
}
 
function Language() {
  return <p>{navigator.language}</p>;
}
 
function useCurrentPath() {
  return location.pathname;
}

Correct

// Read browser values after hydration.
function Sidebar() {
  const [wide, setWide] = useState(false);
  useEffect(() => {
    const update = () => setWide(window.innerWidth > 1024);
    update();
    window.addEventListener("resize", update);
    return () => window.removeEventListener("resize", update);
  }, []);
  return <aside className={wide ? "sidebar wide" : "sidebar"} />;
}
 
// Or subscribe with a server snapshot.
function useOnline() {
  return useSyncExternalStore(
    subscribe,
    () => navigator.onLine,
    () => true
  );
}
 
// Use the framework's router instead of location.
function useCurrentPath() {
  return usePathname();
}

Options

This rule has no options.

Messages

The exact ESLint messages:

  • <read> is read during render, but it does not exist when the server renders. The server output and the first browser render disagree (or the server crashes). Read it in useEffect after hydration, or use useSyncExternalStore with a getServerSnapshot.

When not to use it

Disable it for components that truly render only in the browser—for example, ones loaded with next/dynamic and ssr: false or placed inside a client-only boundary. Prefer a file-level exception to turning the rule off for the whole app.