# no-browser-global-in-render

> Reading window in render: React runs that code on the server too, where window, document and navigator do not exist. This rule reports those reads.

Source: https://hydration.jscrate.dev/docs/rules/no-browser-global-in-render
Last updated: 2026-09-18

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 |
| Suggestions | No |
| Options | none |

## What it reports

Reads in [render code](https://hydration.jscrate.dev/docs/eslint#what-counts-as-render) 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 window` and reads behind a `typeof window` / `isBrowser` check:
  [`no-window-render-branch`](https://hydration.jscrate.dev/docs/rules/no-window-render-branch) reports the
  check.
- `localStorage` and `sessionStorage`:
  [`no-storage-in-initial-render`](https://hydration.jscrate.dev/docs/rules/no-storage-in-initial-render).
- `matchMedia`: [`no-match-media-in-render`](https://hydration.jscrate.dev/docs/rules/no-match-media-in-render).
- Initial values of `useState`, `useReducer`, `useRef` and class state:
  [`no-client-only-initial-state`](https://hydration.jscrate.dev/docs/rules/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](https://hydration.jscrate.dev/docs/errors/window-is-not-defined)) or, behind a
check, renders something other than the browser does:

```text
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

```jsx
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

```jsx
// 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](https://hydration.jscrate.dev/docs/guides/client-only-component)). Consider an
`eslint-disable` comment on those files rather than turning the rule off
everywhere.

## Related

- [`no-window-render-branch`](https://hydration.jscrate.dev/docs/rules/no-window-render-branch): the
  `typeof window` checks that guard these reads
- [`no-client-only-initial-state`](https://hydration.jscrate.dev/docs/rules/no-client-only-initial-state):
  browser values in initial state
- [`require-stable-server-snapshot`](https://hydration.jscrate.dev/docs/rules/require-stable-server-snapshot):
  browser reads inside `getServerSnapshot`
- [Browser-only APIs used during render](https://hydration.jscrate.dev/docs/causes/browser-api): the cause
  and its fixes
