Fixing `localStorage is not defined` Error in Next.js

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

Understanding the Error

Encountering the localStorage is not defined error in Next.js happens because localStorage is a browser API, and during server-side rendering (SSR), your code is executed in a Node.js environment where localStorage is not available. To resolve such errors, it is crucial to ensure that localStorage operations are only called when the code is running on the client-side.

Ensuring Client-Side Execution

Next.js 13 introduced a more intuitive file-system routing and added features to distinguish between server and client code. Instead of using flags like use client, you can now make use of new conventions and components to control where your code should run. An effective method is to encapsulate the code that utilizes localStorage within React’s lifecycle methods that only run on the client, such as useEffect(), or using the use prefix with hooks that are designed to run on the client side.

Using React Hooks

Implement client-side only logic within React’s useEffect hook. This hook ensures the enclosed code only runs after the component mounts, which only happens on the client. Below is an example of how to safely access localStorage in a functional component that uses the useEffect hook.

import { useEffect, useState } from 'react';

export default function ComponentWithLocalStorage() {
  const [storedValue, setStoredValue] = useState(null);

  useEffect(() => {
    const data = localStorage.getItem('myKey');
    if (data) setStoredValue(JSON.parse(data));
  }, []); // Empty dependency array ensures useEffect runs once after initial render

  return (
    <div>
      {storedValue && (
        <p>Value from localStorage: {storedValue}</p>
      )}
    </div>
  );
}

Conditionally Loading Components

Another approach is to conditionally render components based on whether the code is executing on the client or the server. Next.js provides a built-in hook called useClientEffect which you can use to replace useEffect when the effect should only run on the client side. Below is an example illustrating how to use it.

import { useClientEffect, useState } from 'next';

export default function Component() {
  const [data, setData] = useState('');

  useClientEffect(() => {
    setData(localStorage.getItem('myKey') || '');
  }, []);

  return (
    <div>
      {data && (
        <p>Client-side data: {data}</p>
      )}
    </div>
  );
}

Dynamic Imports with SSR Disabled

You can also utilize dynamic imports with the { ssr: false } option to ensure that a component or module is only imported and used on the client side. This is particularly helpful when the module itself (not just its usage in your code) assumes a browser environment and could cause issues during SSR.

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

const ClientSideComponent = dynamic(
  () => import('./ClientOnlyComponent'), // Replace with the path to your component
  { ssr: false }
);

export default function Page() {
  return (
    <div>
      <ClientSideComponent />
    </div>
  );
}

Conclusion

By using the methods described such as leveraging useEffect, conditionally rendering with useClientEffect, or dynamically importing with SSR disabled, you can easily circumvent the localStorage is not defined error in your Next.js 13+ projects. It’s essential to think about where and how your code runs, separating client-side operations from server-side whenever necessary.