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.
| Rule | hydration-proof/no-browser-global-in-render |
|---|---|
| What it reports | Disallow reading browser-only globals such as window and document while a component renders |
| recommended / next | Error |
| strict | Error |
| Server Components | Skipped with the next preset (they never hydrate) |
| Suggestions | No |
| Options | none |
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:
typeof windowand reads behind atypeof window/isBrowsercheck:no-window-render-branchreports the check.localStorageandsessionStorage:no-storage-in-initial-render.matchMedia:no-match-media-in-render.- Initial values of
useState,useReducer,useRefand class state:no-client-only-initial-state.
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.
Related
no-window-render-branch: thetypeof windowchecks that guard these readsno-client-only-initial-state: browser values in initial staterequire-stable-server-snapshot: browser reads insidegetServerSnapshot- Browser-only APIs used during render: the cause and its fixes