DEV Community

Shadorux The Hedgehog
Shadorux The Hedgehog

Posted on AI-assisted

Next.js "window is not defined": 5 Common Causes and Fixes

Next.js "window is not defined": 5 Common Causes and Fixes

If you've worked with Next.js, you've probably run into this at some point:

ReferenceError: window is not defined
Enter fullscreen mode Exit fullscreen mode

Your code might work perfectly in a regular React app or even seem fine while you're writing it... only for Next.js to throw an error.

Usually, the problem isn't window itself. It's where and when your code tries to access it.

Here are five common causes and practical ways to fix them.

1. Using browser-only APIs on the server

Next.js can render code on the server, where browser APIs don't exist.

That includes things like:

window
document
localStorage
sessionStorage
navigator
Enter fullscreen mode Exit fullscreen mode

So this can cause an error:

export default function Page() {
  console.log(window.innerWidth);

  return <div>Hello!</div>;
}
Enter fullscreen mode Exit fullscreen mode

One option is to move browser-dependent work into an effect:

'use client';

import { useEffect } from 'react';

export default function Page() {
  useEffect(() => {
    console.log(window.innerWidth);
  }, []);

  return <div>Hello!</div>;
}
Enter fullscreen mode Exit fullscreen mode

useEffect runs after the component has mounted in the browser, so window is available.

2. Accessing browser APIs during the initial render

A component being a Client Component doesn't mean you should access browser APIs anywhere you want.

For example:

'use client';

export default function Page() {
  const width = window.innerWidth;

  return <p>Width: {width}</p>;
}
Enter fullscreen mode Exit fullscreen mode

A safer approach is to initialize your state without window, then update it after mounting:

'use client';

import { useEffect, useState } from 'react';

export default function Page() {
  const [width, setWidth] = useState(null);

  useEffect(() => {
    setWidth(window.innerWidth);
  }, []);

  return <p>Width: {width ?? 'Loading...'}</p>;
}
Enter fullscreen mode Exit fullscreen mode

This is especially useful when working with screen dimensions, browser preferences, storage, or other client-specific information.

3. A third-party library accesses window

Sometimes your code isn't the problem.

A library may access window or document as soon as it's imported. This is common with certain charting libraries, editors, maps, analytics tools, and other browser-heavy packages.

If the component doesn't need server rendering, you can dynamically import it with SSR disabled:

'use client';

import dynamic from 'next/dynamic';

const SomeChart = dynamic(
  () => import('./SomeChart'),
  {
    ssr: false,
  }
);

export default function Page() {
  return <SomeChart />;
}
Enter fullscreen mode Exit fullscreen mode

Now that component will only load in the browser.

This can be a useful fix when a dependency assumes it's always running in a browser environment.

4. Reading localStorage, screen size, or preferences too early

Here's another common example:

const theme = localStorage.getItem('theme');
Enter fullscreen mode Exit fullscreen mode

That works in the browser.

During server rendering?

💥

Instead, read the value after the component mounts:

'use client';

import { useEffect, useState } from 'react';

export default function ThemeToggle() {
  const [theme, setTheme] = useState(null);

  useEffect(() => {
    const savedTheme = localStorage.getItem('theme');
    setTheme(savedTheme);
  }, []);

  return <p>Theme: {theme ?? 'default'}</p>;
}
Enter fullscreen mode Exit fullscreen mode

The same idea applies to APIs such as:

localStorage
sessionStorage
matchMedia
navigator
screen
Enter fullscreen mode Exit fullscreen mode

If the value depends on the user's browser, think carefully about when you're reading it.

5. Your interactive code belongs in a Client Component

With the Next.js App Router, components are Server Components by default unless you mark the appropriate boundary with:

'use client';
Enter fullscreen mode Exit fullscreen mode

For example:

'use client';

import { useEffect, useState } from 'react';

export default function ThemeToggle() {
  const [theme, setTheme] = useState('light');

  useEffect(() => {
    const savedTheme = localStorage.getItem('theme');

    if (savedTheme) {
      setTheme(savedTheme);
    }
  }, []);

  return (
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      Current theme: {theme}
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

Don't turn your entire application into Client Components just because one part needs a browser API.

Keep the client boundary around the interactive parts that need it.

Quick checklist

When you see:

ReferenceError: window is not defined
Enter fullscreen mode Exit fullscreen mode

Check these first:

  • Are you accessing window, document, or another browser API during rendering?
  • Could that code run inside useEffect instead?
  • Is a third-party package accessing browser APIs internally?
  • Could that component be dynamically imported with ssr: false?
  • Does the interactive portion belong in a Client Component?
  • Are you reading localStorage, screen dimensions, or browser preferences before mounting?

Most of these errors come down to the same distinction:

Server code doesn't have a browser.

Once you identify which part of your code assumes one exists, the error becomes much easier to track down.

Still stuck?

I'm Shadorux, a full-stack developer who builds web apps, developer tools, and other projects.

I also offer focused React and Next.js debugging and bug fixes. If you've got an existing project with an error you can't track down, you can find my debugging service on Fiverr.

Portfolio: shadorux.dev

Fiverr: https://www.fiverr.com/s/kXxaxjL

Top comments (0)