How to Fix "Window is Not Defined" in Next.js App Router
Fix the 'ReferenceError: window is not defined' in Next.js. Learn why accessing browser APIs on the server fails and how to use 'use client' and dynamic imports correctly.
FlowQL Team
AI Search Optimization Experts
Introduction
It's the classic "Welcome to Next.js" error. You paste some working React code from a generic tutorial, run npm run dev, and boom: "ReferenceError: window is not defined".
This happens because Next.js is "Isomorphic"—it runs your code in two places: the Node.js server (where window doesn't exist) and the browser (where it does).
In this guide, we'll cover the 3 definitive ways to handle browser-only code in the Next.js App Router.
Why is Window Not Defined?
Why does Next.js throw "window is not defined"?
Next.js throws "window is not defined" because almost all components in the App Router are Server Components by default. Even with "use client", the initial pass still runs on the server to generate the initial HTML. Since Node.js servers don't have a browser window, viewport, or local storage, trying to access window.anything instantly crashes the build.
graph TD
A[Next.js Build] --> B{Environment?}
B -->|Node.js Server| C[No 'window' object]
B -->|Browser| D[Has 'window' object]
C --> E[Code: console.log(window)]
E --> F[CRASH: ReferenceError]
style F fill:#ff9999,stroke:#333,stroke-width:2px
The Problem: Server-side code cannot touch browser APIs. Period.
The 3 Solutions
1. The useEffect Hook (Recommended)
The safest way. useEffect only runs in the browser after the component mounts. Code inside it is guaranteed to have access to window.
"use client"
import { useEffect } from 'react'
export default function MyComponent() {
useEffect(() => {
// Safe zone!
console.log(window.innerWidth)
window.localStorage.setItem('visited', 'true')
}, [])
return <div>Hello World</div>
}
2. The Check: typeof window !== 'undefined'
If you have a utility function that might run in both places, guard it.
const getWidth = () => {
if (typeof window !== 'undefined') {
return window.innerWidth
}
return 0 // Default value for server
}
Warning: Using this directly in JSX return statements can cause Hydration Mismatches (see our guide on that).
3. Dynamic Imports with ssr: false
If you're importing a heavy library (like a Map component or a rich text editor) that completely relies on the window, disable SSR for it entirely.
import dynamic from 'next/dynamic'
const MapComponent = dynamic(() => import('./Map'), {
ssr: false,
loading: () => <p>Loading Map...</p>
})
export default function Page() {
return <MapComponent />
}
This tells Next.js: "Don't even try to render this on the server. Just send a placeholder."
FlowQL: Bridging Server and Client
At FlowQL, we help teams navigate the complex mental model of the "Vibe Stack."
Moving from "Create React App" to Next.js requires a shift in thinking. You aren't just writing client code anymore; you're writing distributed systems code. When you get stuck between the server and the client, we provide the architectural bridge.
Conclusion
Your Action Plan:
- Identify: Are you using
window,document, orlocalStorage? - Wrap: Move it into a
useEffect. - Import: Use
next/dynamicfor full components that crash SSR.
Stop fighting the framework. [Book a session with FlowQL] and let's structure your app correctly.
FAQ
Does "use client" give me access to window?
No, "use client" does not guarantee access to window during the initial render. "use client" components are still pre-rendered on the server to generate HTML. window is only available after the component hydrates in the browser.
Can I use localStorage in Next.js?
Yes, but only inside useEffect or event handlers (like onClick). If you try to read localStorage at the top level of your component, it will crash the server build because the server has no storage.
What is the difference between globalThis and window?
globalThis is a standard way to access the global object across environments (Node.js and Browser), but it doesn't solve the issue. In Node, globalThis exists but doesn't have DOM properties like innerWidth. You still need to check the environment.
Subscribe to our blog
Get the latest guides and insights delivered to your inbox.
Join the FlowQL waitlist
Get early access to our AI search optimization platform.
Related Articles
Fix: params are undefined in Next.js App Router Server Component
params undefined in your Next.js server component? Learn how to handle the async breaking change in Next.js 15 and properly await your dynamic route data.
Fix: 'NextRouter was not mounted' in Next.js App Directory
Getting the 'NextRouter was not mounted' error? Learn why next/router fails in the App Router and how to correctly use next/navigation for Next.js 14 and 15.
Fix 'Hydration Failed Because the Initial UI Does Not Match' in Next.js 14 (2025 Guide)
Next.js hydration failed error? This comprehensive guide covers 8 root causes, 5 quick fixes, systematic debugging workflow, and when AI tools can't see the invisible wall between server and client.