Skip to content

Next.js error

ReferenceError: window Is Not Defined

The error

ReferenceError: window is not defined

Your code ran on a server, where there is no browser. The fix depends on whether the value is needed to render or only after the page is interactive.

window, document, localStorage and navigator are provided by a browser. A Server Component, a route handler, generateStaticParams, and the server pass of a Client Component all run in Node, where none of them exist.

The error is telling you the code ran somewhere it did not expect to. Which fix applies depends on when the value is needed.

If it is needed only after the page is interactive

Move it into an effect. This covers measuring an element, reading localStorage, subscribing to a resize or scroll, setting up an observer - anything that is a consequence of being in a browser rather than an input to the markup.

'use client';
 
const [width, setWidth] = useState<number | null>(null);
 
useEffect(() => {
  const onResize = () => setWidth(window.innerWidth);
  onResize();
  window.addEventListener('resize', onResize);
  return () => window.removeEventListener('resize', onResize);
}, []);

Effects do not run on the server, so there is nothing to guard.

If it is needed to render

You have a decision, not a workaround. Render a stable value on both passes and correct it after mount:

const [theme, setTheme] = useState<'light' | 'dark'>('light'); // both passes agree
useEffect(() => setTheme(readStoredTheme()), []);

The first paint shows the default; the stored value arrives immediately after. If that flash is unacceptable - a theme is the usual case - the value has to reach the server instead, as a cookie read in a Server Component, so it is in the HTML from the start.

What you must not do is branch on typeof window during render. The server renders one branch and the browser renders the other, which is a hydration mismatch by construction.

If it is a library that touches window on import

Some packages read window at module scope, so importing them is enough to throw. Import them lazily instead:

'use client';
import dynamic from 'next/dynamic';
 
const Map = dynamic(() => import('./map'), { ssr: false });

Be deliberate here. ssr: false means the component is absent from the HTML, so it is invisible to a crawler and arrives after hydration for everyone else. That is the correct trade for a map widget and the wrong one for your main content.

Finding it during the build

next build prerenders every static route, which is why the build is where this usually surfaces. The error names the module but not always the route; if the stack is unhelpful, the route list in the build output narrows it - the first route that fails is the one importing the offending module.

A related shape worth knowing: the same code in middleware fails for a different reason. The edge runtime is not Node either, so Node APIs are missing there even though you are on a server.

Related questions

Why does it work in development and fail on build?
It usually fails in both, but `next dev` renders on demand and you may not have hit the route. `next build` prerenders every static page, so it visits all of them and finds the one you did not.
Is `typeof window !== 'undefined'` a real fix?
It is a guard, not a fix, and using it during render creates a hydration mismatch: the server takes one branch and the client takes the other. It is safe in an effect or an event handler, where only the browser runs the code anyway.