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.
| 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 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:
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)
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.
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