Hydration Proof

Search documentation

Find a page or section

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

Reading window in render: React runs that code on the server as well, where window, document and navigator do not exist, so the server either crashes or renders something other than the browser. no-browser-global-in-render reports reads of browser-only globals in render code; read them 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 uses scope analysis: a prop, parameter or local variable called window or location is not the global and is not reported. Globals that exist on the server as well (Date, Math, Intl, crypto, performance) are left to the rules for them.

Not reported here, because a more specific rule does:

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

On the server these globals do not exist, so 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

What ESLint prints for this rule, word for word:

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

In components that only render in the browser (for example loaded with next/dynamic and ssr: false, or rendered inside a client-only boundary). Consider an eslint-disable comment on those files rather than turning the rule off everywhere.