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
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
So this can cause an error:
export default function Page() {
console.log(window.innerWidth);
return <div>Hello!</div>;
}
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>;
}
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>;
}
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>;
}
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 />;
}
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');
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>;
}
The same idea applies to APIs such as:
localStorage
sessionStorage
matchMedia
navigator
screen
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';
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>
);
}
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
Check these first:
- Are you accessing
window,document, or another browser API during rendering? - Could that code run inside
useEffectinstead? - 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)