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.
Window
and document
are unavailable on the server. So you'll run into errors such as ReferenceError: window is not defined
if you are trying to access document
or window
properties such as local storage
.
To avoid these undefined errors at compile and build time, you have different options.
A.1 - Run Only for browser users
You can run a simple check, and only if a user is a browser user run your code. This way server will not take your code into consideration.
Since process.browser
is deprecated you must use typeof window
.
if (typeof window !== "undefined") {
// Write your client-side statements here.
window.localStorage.getItem("key");
window.localStorage.setItem("key", "value");
}
Note: this is good way if you want to do simple operations and other variables in other components do not depend on this value or you will get error such as "can not read the property of undefined"
. If your code do so or you have many similar operation in your code, Look for solution "A.2".
A.2 - Actual window & dummy window (Recommended):
"A.1" is good and it'll work, but you need to explicitly check if it is a client-side rendered component or server-side every time.
To avoid this issue you need to create an mock window
object.
then use mock window
for server-side logic and actual window
for client-side.
let WINDOW = {};
if (typeof window !== "undefined") {
// When code is on client-side. So we need to use actual methods and data.
WINDOW = window;
} else {
// When code is on server-side.
// Other component are mostly server-side and need to match their logic and check their variable with other server-side components and logics.
// So following code will be use for them to pass the logic checking.
WINDOW = {
document: {
location: {},
},
localStorage: {
getItem :() => {},
setItem :() => {}
},
};
}
export default WINDOW;
Note that: Depending on what property of window
, document
,... or what methods of those property you have used, your implementation details will be different.
Now use WINDOW
instead of window
in your code:
WINDOW.localStorage.getItem("key");
WINDOW.localStorage.setItem("key", "value");
Example of use:
I created a custom hook for using local storage
:
export const useLocalStorage = (key) => {
// returns value related to initial key.
const item = WINDOW.localStorage.getItem(key);
// return an function that will save given value to initial key
const setItem = (value) => WINDOW.localStorage.setItem(key, value);
return [item, setItem];
};
Warning:
remember, never create a function for window type checking.
it always must be an explicit type checking or it will not work
Wrong way:
const hasWindow = () => {
return typeof window !== "undefined"
}
if (hasWindow()) {
// client-side operation such as local storage.
localStorage.setItem(key, value)
}
if (!hasWindow()) {
// server-side code
}
read more and reason for this problem: NextJS GitHub issues page
B - EventListener and useEffect hook
If you are simply registering EventListener
such as a "Scroll" event to your window
object there is no need to go through all these troubles.
The React
way to solve this issue would be to use the useEffect
hook. Which only runs at the rendering phase, so it won't run on the server.
useEffect(() => {
window.addEventListener("scroll", () => { console.log("scroll!"); });
}, []); // empty dependencies array means "run this once on first mount"
Tip: The way you should use useEffect
is to register and unregister the listeners on "mount"/"unmount". But you could also just register on "mount" and ignore any other rendering event, like what we did in the example.
Note: Make JavaScript logic Independent to components
useEffect
is a hook and it must be used in functional components.
But at the same time, it's wrong for our JS codes that are unrelated to a specific component to depend on them.
Read more and solve this issue at:
Convert and Execute JS code as React Component
C - No Server Side Rendering
You can make it so 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
.
But it is not recommended to use unless you really know what you are doing and using it at the right place. Because misuse of it makes your next project meaningless (You are not taking advantage of a core NextJS feature (SSR
)).
40% of this post is thanks to these guys and a copy-paste of similar post written by them:
Ibrahim Adeniyi
Vincent Voyer
Top comments (0)