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.