Skip to main content

Command Palette

Search for a command to run...

Avoiding Common Mistakes with Browser APIs in Next.js

Published
4 min read
Avoiding Common Mistakes with Browser APIs in Next.js
R

Full Stack Developer with experience in building end-to-end encrypted chat services. Currently dedicated in improving my DSA skills to become a better problem solver and deliver more efficient, scalable solutions.

One common mistake that beginners make when working with Next.js is incorrectly using browser APIs like window.localStorage in client components. Understanding how Next.js handles server-side rendering (SSR) and client-side rendering (CSR) is crucial for avoiding this mistake. In this article, we'll explore why accessing browser APIs too early can lead to errors, and we'll cover three effective ways to handle this situation.

The Problem: Using window.localStorage Incorrectly

In Next.js, all components (including client components) are rendered on the server side initially to generate static HTML. During this process, if a client component attempts to access browser-specific objects like window or localStorage, it will cause an error because these objects are not available in the Node.js environment that the server runs on.

Why Does This Happen?

Next.js pre-renders the entire page on the server, including client components, to generate the HTML that gets sent to the client. Even though client components only "hydrate" (i.e., become interactive) on the client side, they still run once on the server. When you directly use window or localStorage in this phase, the server doesn't know how to handle these browser-specific objects, which results in an error.

Here's an example of what not to do:

"use-client";

const MyComponent = () => {
    const favorite = localStorage.getItem('favorite'); // This will cause an error on the server

    return <div>{favorite ? "Favorited" : "Not Favorited"}</div>;
};

export default MyComponent;

This code will throw an error on the server because localStorage doesn't exist in a Node.js environment.

Solutions: Handling Browser APIs Correctly

To fix this, you need to ensure that browser APIs like localStorage are only accessed on the client side, after the component has mounted. Here are three methods to handle this properly:


1. Conditionally Check for window

You can use a condition to check whether the window object is defined. This ensures that your code runs only on the client, not during server-side rendering.

"use-client";

const MyComponent = () => {
    let favorite = false;

    // Only access `localStorage` if `window` is defined (i.e., on the client side)
    if (typeof window !== "undefined") {
        favorite = localStorage.getItem('favorite') === 'true';
    }

    return <div>{favorite ? "Favorited" : "Not Favorited"}</div>;
};

export default MyComponent;

How It Works:

  • The condition typeof window !== 'undefined' ensures that localStorage is only accessed when the component is running on the client side, preventing the error on the server.

2. Using useEffect Hook

Another method is to use the useEffect hook, which only runs on the client side after the initial render. Since useEffect does not run during SSR, it’s a safe place to access browser-specific APIs like localStorage.

"use-client";
import { useEffect, useState } from 'react';

const MyComponent = () => {
    const [favorite, setFavorite] = useState(false);

    useEffect(() => {
            const favorite = localStorage.getItem('favorite');
            setFavorite(favorite === 'true');
    }, []); // Empty dependency array ensures it runs once after the component mounts

    return <div>{favorite ? "Favorited" : "Not Favorited"}</div>;
};

export default MyComponent;

How It Works:

  • useEffect ensures that the browser-specific code is executed only after the component has been mounted on the client. Since it doesn't run during SSR, there’s no risk of accessing window or localStorage on the server.

3. Using Dynamic Imports with ssr: false

You can also use Next.js’s dynamic import feature to load a component only on the client, completely skipping server-side rendering for that specific component. Setting ssr: false ensures that the component won't even attempt to render on the server.

import dynamic from 'next/dynamic';

// Dynamically import the component with SSR disabled
const MyComponent = dynamic(() => import('./MyComponent'), { ssr: false });

export default function Page() {
    return (
        <div>
            <h1>My Page</h1>
            <MyComponent />
        </div>
    );
}

And inside MyComponent:

"use-client";

const MyComponent = () => {
    const favorite = localStorage.getItem('favorite');

    return <div>{favorite ? "Favorited" : "Not Favorited"}</div>;
};

export default MyComponent;

How It Works:

  • By setting { ssr: false }, the MyComponent will only be rendered on the client side, entirely bypassing the SSR process. This means you can safely use window.localStorage without worrying about server-side errors.

Why This Happens: Understanding Hydration and SSR

Next.js pre-renders pages to generate static HTML, even for client components, in order to send it to the client quickly. After this initial render, React hydrates the page by attaching event handlers and re-rendering interactive parts. During SSR, the server can't run browser-specific APIs like localStorage or access window, leading to errors if you're not careful.

Conclusion

Using browser APIs like localStorage in Next.js client components requires careful handling to avoid server-side errors. By using conditional checks, the useEffect hook, or dynamic imports with ssr: false, you can ensure that browser-specific code only runs on the client. Understanding how Next.js handles SSR and hydration is key to building efficient and error-free applications.

You Don't Know Next.js

Part 7 of 16

This series uncovers common mistakes, misconceptions, and best practices in Next.js. Dive into real-world examples, tips, and optimizations to elevate your skills, enhance performance, and avoid pitfalls in your Next.js projects.

Up next

Handling Third-Party Components in Next.js: Avoiding Common Mistakes

When building with Next.js, dealing with third-party components can sometimes cause issues, especially if they aren't correctly configured for client-side rendering. A common mistake developers make is incorrectly using third-party components that re...

More from this blog

Rishi Bakshi Blog

19 posts

Explore state placement, custom hooks, and key design patterns for scalable React apps. 💡 Discover Feature, UI, Page, and Compound components. 🚀 Transition from junior to mid-senior level coding! 📈