Fixing LocalStorage Not Defined Error in Next.js

Updated: January 1, 2024 By: Guest Contributor Post a comment

Encountering an error such as ReferenceError: localStorage is not defined in your Next.js 13 project is a common issue that stems from the server-side rendering nature of Next.js. Since localStorage is a global object that’s available only in the browser’s window context, you’ll run into this error if your code attempts to access it during server-side rendering or static generation, when the window object doesn’t exist.

Here we’ll explore solutions that will ensure you’re using localStorage only on the client side.

Conditional Server-Side Checks

To avoid trying to access localStorage on the server-side, you can use a conditional check to determine if the code is being executed on the client. In Next.js 13, one of the most straightforward approaches is to check if the window object is defined before attempting to access localStorage.

One common place to perform this check is in useEffect hooks which run on the client side after a component mounts:

import { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
    if (typeof window !== 'undefined') {
      // You can safely use localStorage here
      localStorage.setItem('myItem', 'value');
    }
  }, []);

  // The rest of your component...
}

Using Next.js Custom Hooks

To encapsulate the behavior and make it reusable across components, you can create a custom hook. This hook will abstract the functionality related to localStorage by checking the window object’s existence and storing data only on the client side.

import { useState, useEffect } from 'react';

function useLocalStorage(key, initialValue) {
  // Initiate state with given value or retrieve from localStorage
  const [storedValue, setStoredValue] = useState(() => {
    if (typeof window === 'undefined') {
      return initialValue;
    }
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(error);
      return initialValue;
    }
  });

  // Update localStorage when state changes
  useEffect(() => {
    if (typeof window !== 'undefined') {
      window.localStorage.setItem(key, JSON.stringify(storedValue));
    }
  }, [storedValue]);

  return [storedValue, setStoredValue];
}

export default useLocalStorage;

By using this custom hook, localStorage is accessed correctly with client-side checks put neatly in place.

Dynamically Loading Components with No SSR

In some scenarios, you might want to take advantage of Next.js’s dynamic imports to load components only on the client side. This approach is useful when you have components that heavily rely on the browser environment. Here’s how you can do it:

import dynamic from 'next/dynamic';
import React from 'react';

// Import your component using dynamic import and disable SSR
const ClientSideComponent = dynamic(
  () => import('./ClientSideComponent'),
  { ssr: false }
);

function MyPage() {
  // Your ClientSideComponent will only render on the client-side
  return ;
}

By setting the { ssr: false } option, Next.js omits the component during the server-side render pass, thus deferring any client-side code, like usage of localStorage, till it actually runs in the browser.

Wrap Up

In summary, by understanding Next.js’s rendering process and using a conditional check, custom hooks or dynamic imports with no SSR, we can navigate through the challenges posed by server-side rendering and safely integrate localStorage within our projects.