ReferenceError: window is not defined, explained and fixed

window exists only in browsers. This error means code that touches window (or document, localStorage, navigator) executed somewhere else: Node.js, a test runner, or the server pass of server-side rendering in frameworks like Next.js, where components render once on the server to produce HTML and again in the browser. The same component can be correct in the browser and still crash the render if the browser-only line runs during that first server pass.

Find where it runs

  1. Read the stack trace's context: if the frames mention server rendering, a page's data functions, or a build step (static generation), the line runs on the server. If it only fails in tests, the test environment is Node-like and lacks a DOM.
  2. Look for module-scope code: window touched at the top level of a file, in a class field, or in a variable initializer runs at import time, before any component or effect, and is the most common hiding place.

The fixes, in order

  1. Move browser-only work into effects and event handlers in React (useEffect runs only in the browser), or the framework's client-only lifecycle hook. Rendering must be possible without a window; effects are where windows exist.
  2. Guard the unavoidable spots: if (typeof window !== 'undefined') { ... } around the specific access, used sparingly for libraries and initialization code, not wrapped around whole components.
  3. Load browser-only libraries dynamically on the client: in Next.js, dynamic(() => import('lib'), { ssr: false }); elsewhere, a dynamic import() inside an effect, so the server never evaluates the library at all.
  4. For values needed during render (stored theme, saved preference), render a neutral default on the server and reconcile after mount; the server cannot know the browser's stored values, and pretending otherwise also causes hydration mismatches.
  5. In tests, choose a DOM environment (for example jsdom in Vitest or Jest) for suites that render UI, reserving the plain Node environment for pure logic.

Common questions

Why does it work after a refresh sometimes and crash on first load? Client-side navigation renders only in the browser, so the page works; a full load or refresh renders on the server first and hits the error. That asymmetry is the signature of an SSR-only bug.

Is checking typeof window everywhere the right pattern? No. It is a targeted tool for initialization and library code. Needing it in many components usually means browser-only logic belongs in effects or a client-only wrapper component.

Does document fail the same way? Yes, document, localStorage, sessionStorage, navigator and location are all browser globals with the same server problem and the same fixes.

Related errors

Text content does not match server-rendered HTML · Next.js hydration error · Require is not defined