ReferenceError: Window is Not Defined in Gatsby

Abstract image used to represent ReferenceError: Window is Not Defined in Gatsby
Image by Laurent Perren.

In Brief

window and document do not exist during Gatsby's serverside build, so browseronly code needs to run behind a guard, inside the right lifecycle timing, through a conditional import, or behind an SSRsafe fallback. The same shape of bug appears whenever browser globals leak into serverrendered code.

In my current project, I oversee three associate developers; these are hyperintelligent junior developers, fresh out of code camp. I've always enjoyed working in a mentoringtype role, especially when their questions can leave you unsure of the 'why' behind some of the things we do every day in web development.

Today though, I was approached about an incredibly common earlysteps issue when attempting to build a Gatsby site:

WebpackError: ReferenceError: window is not defined
Screenshot of Terminal during a Gatsby build displaying the error message: 'WebpackError: ReferenceError: window is not defined'.

This can be particularly vexing when you've inevitably been developing and running the site locally via gatsby develop with no hint of an issue. Fortunately, the answer is very, very simple.


The Cause

It may be very obvious what the issue is, but just in case: when running a build within the Node.js environment, browser Global Variables such as window or document do not exist, so any code that references them will fail unless suitably safeguarded. There are two very straightforward ways around this:

Ensure that window and/or document is defined before calling

Adding a little safeguarding to your code will ensure that we don't end up in a situation where you're attempting to call browser variables that don't exist. This isn't going to be as simple as just testing !window, but it's also not much more complicated:

// if true, then we are rendering in-browser
typeof window !== 'undefined'

If the operation also needs document, test it independently with typeof before reading it. For example, code that changes a class on document.documentElement needs the document as well as the window:

typeof window !== 'undefined' && typeof document !== 'undefined'

Move the code into the componentDidMount lifecycle

Inside a React component, browser side effects can run in componentDidMount() or useEffect(), which do not run during server rendering. Keep the server output and the first browser render consistent, then update browserdependent state after mounting. A guard prevents a missingglobal error; it does not make different initial markup safe to hydrate.

useEffect(() => {
  // global browser variables will always exist here
}, []);
Photograph of a stack of wooden blocks by Volodymyr Hryshchenko on Unsplash.

Issues with Third‑Party Modules

It's all very well and good knowing how to resolve these types of build errors within your own code, but what about when the issue lies within a thirdparty module which simply assumes that window always exists? Aside from opening a ticket and hoping that the developer sees the error in their ways, there are a couple of options available.

Conditionally Require a Module

Expanding on the safeguarding we discussed above, it is possible to conditionally require a module within your code:

if (typeof window !== 'undefined') {
  const module = require('module');
}

Or inline using a simple ternary:

const module = typeof window !== 'undefined' ? require('module') : null;

Guard uses of the imported module too, and keep its browser work out of server rendering. If it changes what the component displays, start with the same fallback on the server and in the browser, then update after mounting. Merely checking typeof window inside the render path can otherwise produce different initial markup.

Use a dummy placeholder during build

In Gatsby, you can also tie into the Gatsby Node API and simply swap the offending module out during the SSR build process.

Direct from the Gatsby docs on the subject, simply add the following code to your gatsby-node.js file. If your project doesn't have a gatsby-node.js file yet, you can simply create one in the root of your project and it will be picked up next time you run Gatsby.

exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
  if (stage === 'build-html' || stage === 'develop-html') {
    actions.setWebpackConfig({
      module: {
        rules: [
          {
            test: /naughty-module/,
            use: loaders.null(),
          },
        ],
      },
    });
  }
};

I explicitly mention Gatsby in the title and throughout this article, but it's worth mentioning that the exact same is true if you come across this error on buildtime in any serverside rendered framework, like Next.js.

Postscript

June 2026: This Gatsby debugging note is still useful when browseronly code leaks into a serverside build. If this kind of error is one symptom of a wider fragile Gatsby estate, the Gatsby buildtimes article can help decide whether to fix the existing build or plan a migration.

Planning a platform change?

I help teams make difficult platform work clearer, from architecture decisions and migrations to launch recovery, performance, and search visibility.