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!")
});
Then it will fail with "ReferenceError: window is not defined":
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
}
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
}
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;
}
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>
);
}
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;
}
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;
}
// 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>
);
}
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:
Latest comments (34)
Thanks alot, 3rd solution worked for me, i am new in next js and used the reactjs-social-login npm dependency.
And were having a page map to the login in single page but whenever i tried to refresh page it was throwing "Window is not defined" so i just copied social login button and dependencies and created new functional component and dynamically imported in parent login page and it's working fine.
For anyone still dealing with this issue, I used the isBrowser check along with Reacts lazy module to lazyload the import and Suspense wrapped the component being rendered.
Hello,
I am trying to convert html template into reactjs component in reactjs app. I am injecting the js required like this shown in below image.
the js file contains initialization and method invoke as shown in below image.
and i am getting error like this as shown below image, how do i fix it or rather a good way how it's done so i can use every plugin mentioned in this file without error. please help me on this.
It really helpful.
i had to create an account to say Thank you So much the third solution saved my day ^^
Thank you so much, the second solution worked perfectly for me
Thanks for this helpful article
Thanks a lot ...
if (typeof window !== "undefined") {
// browser code
}
this one works for me....
I wanted to redirect to other page using window.location.replace....but several times it threw the error that window is not defined....Basically in HOC there was a class component and inside the render block before the returnning the Original component I wanted to check if a variable is true or not....Hence I made a condition which will redirect to other page if variable is false....
Thanks again!
As a good practice, I would suggest to convert this into a customHook like that:
so you can use it in multiple places like:
This is just an example, it can be extended adding an "element" parameter and some logic for it in the hook so you can get the scroll position of any element and get back the value to do something with it in the callback.
This avoid the need of creating different functions that performs almost the same actions and it's more in line with what hooks are (high order functions).
Even better
Thanks π