A typeof window !== 'undefined' hydration mismatch happens because the check
is false on the server and true in the browser, so a component that branches
on it renders different output on each side. no-window-render-branch
reports environment checks and flags such as isServer that change what a
component renders.
| Rule | hydration-proof/no-window-render-branch |
|---|---|
| What it reports | Disallow rendering different output depending on whether the code runs on the server or in the browser |
| recommended / next | Error |
| strict | Error |
| Server Components | Skipped with the next preset (they never hydrate) |
| Suggestions | No |
| Options | none |
What it reports
In render code:
typeof Xcompared with a string (typeof window !== 'undefined','undefined' == typeof document,typeof window.matchMedia === 'function'), whereXis a browser-only global such aswindow,document,navigator,localStorage,sessionStorage,matchMedia,IntersectionObserverorrequestAnimationFrame.'window' in globalThisand similarinchecks on the global object.globalThis.window/globalThis.documentused as a condition.- Environment flags used as the condition of
if,? :,&&,||or??:isServer,isBrowser,isClient,canUseDOM,canUseDom,isSSR,IS_BROWSER,IS_SERVER,IS_CLIENT(imports, module-level constants or globals, also as properties such asExecutionEnvironment.canUseDOM),process.browserandimport.meta.env.SSR.
Local variables and props are not flags: const isClient = useIsClient() is
the recommended pattern and is not reported. A local assigned from a check
(const isClient = typeof window !== 'undefined') is reported once, at the
check.
Not reported here:
- checks in effects, event handlers and other code that runs after hydration;
- checks in the initial value of
useState,useReducer,useRefor class state:no-client-only-initial-statereports those; - checks that guard a
localStorage/sessionStorageread or amatchMediacall:no-storage-in-initial-renderandno-match-media-in-renderreport the read.
Browser reads behind a reported check
(typeof window !== 'undefined' ? window.innerWidth : 0) are not reported
again by no-browser-global-in-render.
Why the typeof window !== 'undefined' hydration mismatch happens
The check is false on the server and true during hydration, so the two
renders take different branches:
server HTML: <div class="chart-placeholder"></div>
client render: <canvas class="chart"></canvas>React reports the mismatch and renders the whole page again on the client, which defeats server rendering.
Incorrect
function Chart({ data }) {
if (typeof window === "undefined")
return <div className="chart-placeholder" />;
return <Canvas data={data} />;
}
function Greeting() {
const isBrowser = typeof window !== "undefined";
return (
<p>{isBrowser ? `Hello from ${window.location.hostname}` : "Hello"}</p>
);
}Correct
function useIsClient() {
return useSyncExternalStore(
() => () => {},
() => true,
() => false
);
}
function Chart({ data }) {
const isClient = useIsClient();
if (!isClient) return <div className="chart-placeholder" />;
return <Canvas data={data} />;
}
function Greeting() {
const [host, setHost] = useState(null);
useEffect(() => setHost(window.location.hostname), []);
return <p>{host ? `Hello from ${host}` : "Hello"}</p>;
}Both render the server's output during hydration and switch after it. With
Next.js, next/dynamic with ssr: false
is another way to render a component only in the browser.
Options
This rule has no options. Add your own flag names with the
shared setting environmentFlags:
{
settings: { 'hydration-proof': { environmentFlags: ['__SERVER__', 'isNode'] } },
}Messages
What ESLint prints for this rule, word for word:
<check>makes the render output depend on where the code runs. The server takes one branch and the browser the other while hydrating, so the HTML does not match. Render the same output first and switch after mount (useEffect and state), or use useSyncExternalStore with a getServerSnapshot.
When not to use it
In code that is never server-rendered.
Related
no-browser-global-in-render: browser reads outside a checkrequire-stable-server-snapshotreports checks insidegetServerSnapshot.- Browser-only APIs used during render: the cause and its fixes
- Client-only components: the patterns that replace the check
- Hydration failed because the server rendered HTML didn't match the client