DEV Community

loading...

How to solve "window is not defined" errors in React and Next.js

Vincent Voyer
Building 🏗 https://turnshift.app, Shift Scheduling for Slack Teams. Also: https://nextjsnews.com https://github.com/vvo https://dev.to/vvo Before: JavaScript and support @algolia .
Updated on ・3 min read

Next.js is a React framework with pre-rendering abilities. This means that for every page, Next.js will try to generate the HTML of the page for better SEO and performance.

This is why, if you're trying to do this:

// components/Scroll.js
window.addEventListener("scroll", function() {
  console.log("scroll!")
});
Enter fullscreen mode Exit fullscreen mode

Then it will fail with "ReferenceError: window is not defined":

React error

Because in the Node.js world, window is not defined, window is only available in browsers.

There are three ways to solve that:

1. First solution: typeof

While you can't use:

if (window !== undefined) {
  // browser code
}
Enter fullscreen mode Exit fullscreen mode

Because this would try to compare a non-existent variable (window) to undefined, resulting in the mighty "ReferenceError: window is not defined". You can still use:

if (typeof window !== "undefined") {
  // browser code
}
Enter fullscreen mode Exit fullscreen mode

Because typeof won't try to evaluate "window", it will only try to get its type, in our case in Node.js: "undefined".

PS: Thanks to
Rogier Nitschelm
for reminding me about this. I initially tried to do if (typeof window !== undefined) and this failed hard because of the reasons mentioned earlier.

The other solutions below are more exotic but still worth it.

2. Second solution: the useEffect hook

The "React" way to solve this issue would be to use the useEffect React hook. Which only runs at the rendering phase, so it won't run on the server.

Let's update our scroll.js component:

// components/Scroll.js

import React, { useEffect } from "react";

export default function Scroll() {
  useEffect(function mount() {
    function onScroll() {
      console.log("scroll!");
    }

    window.addEventListener("scroll", onScroll);

    return function unMount() {
      window.removeEventListener("scroll", onScroll);
    };
  });

  return null;
}
Enter fullscreen mode Exit fullscreen mode

What we've done here is to turn our initial JavaScript file into a true React component that then needs to be added to your React tree via:

// pages/index.js

import Scroll from "../components/Scroll";

export default function Home() {
  return (
    <div style={{ minHeight: "1000px" }}>
      <h1>Home</h1>
      <Scroll />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Tip: The way we use useEffect in the example is to register and unregister the listeners on mount/unmount. But you could also just register on mount and ignore any other rendering event, to do so you would do this:

// components/Scroll.js

import React, { useEffect } from "react";

export default function Scroll() {
  useEffect(function onFirstMount() {
    function onScroll() {
      console.log("scroll!");
    }

    window.addEventListener("scroll", onScroll);
  }, []); // empty dependencies array means "run this once on first mount"

  return null;
}
Enter fullscreen mode Exit fullscreen mode

3. Third solution: dynamic loading

A different solution is to load your Scroll component using dynamic imports and the srr: false option. This way your component won't even be rendered on the server-side at all.

This solution works particularly well when you're importing external modules depending on window. (Thanks Justin!)

// components/Scroll.js

function onScroll() {
  console.log("scroll!");
}

window.addEventListener("scroll", onScroll);

export default function Scroll() {
  return null;
}
Enter fullscreen mode Exit fullscreen mode
// pages/index.js

import dynamic from "next/dynamic";

const Scroll = dynamic(
  () => {
    return import("../components/Scroll");
  },
  { ssr: false }
);

export default function Home() {
  return (
    <div style={{ minHeight: "1000px" }}>
      <h1>Home</h1>
      <Scroll />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

If you do not need the features of useEffect, you can even remove its usage completely as shown here.

Finally, you could also load your Scroll component only in _app.js if what you're trying to achieve is to globally load a component and forget about it (no more mount/unmount on page change).

I have used this technique to display a top level progress bar with NProgress in this article:

Discussion (20)

Collapse
rowin1125 profile image
Rowin Mol • Edited

Niceee!

If you don't want to have a lot of boilerplate code, you could simple write:

const isBrowser = window && window.addEventListener(.......blabla)
Enter fullscreen mode Exit fullscreen mode

and use this variable as a conditional.

Also if you use the first option, please remember to unsubscribe your eventListener in the useEffect or you gonna have a bad time 😭 .

useEffect(function onFirstMount() {
    function onScroll() {
      console.log("scroll!");
    }

    window.addEventListener("scroll", onScroll);

   return () => {
      window.removeEventListener("scroll");
    }
  }, []);
Enter fullscreen mode Exit fullscreen mode
Collapse
vvo profile image
Vincent Voyer Author • Edited

Hey there Rowin, you might have a different setup but const isBrowser = window won't work at least in Next.js pre-rendering. You'll get the same error (just tried it again). I think anytime such code would go through Node.js it would fail, as shown in a REPL:

> node
Welcome to Node.js v12.18.0.
Type ".help" for more information.
> window && "ok"
Uncaught ReferenceError: window is not defined
Enter fullscreen mode Exit fullscreen mode

As for unsubscribing the listener, this is already done in the code samples from the article so maybe you missed that or there's some other place I forgot to do it?

Thanks

Collapse
teamroggers profile image
Rogier Nitschelm

Perhaps like this?

const isBrowser = () => typeof window !== "undefined"

// node
isBrowser() // false

// browser
isBrowser() // true
Enter fullscreen mode Exit fullscreen mode
Thread Thread
vvo profile image
Vincent Voyer Author

Yes that would work indeed! Thanks :)

Thread Thread
vvo profile image
Vincent Voyer Author

Just added a new solution and thank you note :)

Collapse
jmsunseri profile image
Justin Sunseri

none of these solutions work well if the problem is in a library you are using

Collapse
vvo profile image
Vincent Voyer Author

Definitely right. When that's the case I am not sure if there's even a solution. Do you know one? If the library tries to read directly from the window object, then it just isn't compatible with Node.js (even if it could I guess).

Maybe you can do something like global.window = {} and provide the necessary code? Let us know!

Collapse
jmsunseri profile image
Justin Sunseri

My solution was to import the component that used the library that used window using the dynamic importer

import dynamic from 'next/dynamic';
...
const Standings = dynamic(() => import('../components/Standings/Standings'), {
  ssr: false,
});
Thread Thread
vvo profile image
Vincent Voyer Author

Thanks, if I am not mistaken, that's the third solution of this current blog post: dev.to/vvo/how-to-solve-window-is-...

Thread Thread
jmsunseri profile image
Justin Sunseri

I feel silly now.

Thread Thread
alsmith808 profile image
Thread Thread
vvo profile image
Vincent Voyer Author

Hey Justin, don't, because I am updating the article right now to add that this particular solution works well for libraries you're importing :) Thanks for the tip!

Collapse
polobustillo profile image
PoloBustillo

Nice work
I was struggling a little with adding a function that is not a default export to run with SSR false, maybe this could be helpful for someone:

import dynamic from 'next/dynamic'

const DynamicComponent = dynamic(() =>
import('../components/hello').then((mod) => mod.Hello)
)

Collapse
truongdinh2 profile image
truongdinh2

thank you, but I only want show model when scroll. It seems re-render so many.

Collapse
bl4ckck profile image
bl4ckck

how to load variable?

example:

var myVar = "bla"

When I use dynamic loading, it returns ReferenceError: window is not defined

Collapse
copperfox777 profile image
copperfox777

Good article!

Collapse
beulahpt profile image
Beulah Sheeba

Hi How can I solve this in react js with out next?

Collapse
vvo profile image
Vincent Voyer Author

The blog post has Next.js in it but really all techniques (but the dynamic loading one) will work everywhere, so try them and let us know

Collapse
doctorderek profile image
Dr. Derek Austin 🥳

Love it, thanks! I didn't know about the Next.js dynamic importer, but that's great for cases like this. 💯

Collapse
natashajaved profile image
natashajaved • Edited

What if I want to find geolocation using navigator object in getStaticProps and make api request on that basis and use the response to fill data in pre rendering. Need to do this for SEO

Forem Open with the Forem app