ReferenceError: Window is Not Defined in Gatsby

In Brief
window and document do not exist during Gatsby's server‑side build, so browser‑only code needs to run behind a guard, inside the right lifecycle timing, through a conditional import, or behind an SSR‑safe fallback. The same shape of bug appears whenever browser globals leak into server‑rendered code.
In my current project, I oversee three associate developers; these are hyper‑intelligent junior developers, fresh out of code camp. I've always enjoyed working in a mentoring‑type 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 early‑steps issue when attempting to build a Gatsby site:
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 browser‑dependent state after mounting. A guard prevents a missing‑global error; it does not make different initial markup safe to hydrate.
useEffect(() => {
// global browser variables will always exist here
}, []);
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 third‑party 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 build‑time in any server‑side rendered framework, like Next.js.
Postscript
June 2026: This Gatsby debugging note is still useful when browser‑only code leaks into a server‑side build. If this kind of error is one symptom of a wider fragile Gatsby estate, the Gatsby build‑times article can help decide whether to fix the existing build or plan a migration.