DEV Community

Cover image for NextJS - Access window and localStorage
ARiyou Jahan
ARiyou Jahan

Posted on • Updated on

NextJS - Access window and localStorage

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.

NextJS - Access window and document and localStorage

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");
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

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];
};
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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)).

Next.JS - Dynamic Import


40% of this post is thanks to these guys and a copy-paste of similar post written by them:
Ibrahim Adeniyi
Vincent Voyer

Oldest comments (0)