No articles found
Try different keywords or browse our categories
Fix: window is not defined in Next.js Project Error
Learn how to fix the 'window is not defined' error in Next.js applications. This comprehensive guide covers client-side only code, dynamic imports, and proper browser API usage.
The ‘window is not defined’ error is a common Next.js issue that occurs when trying to access browser-specific APIs like window, document, or localStorage during server-side rendering. This error happens because Next.js renders pages on the server first, where browser APIs are not available. The error typically appears during development or build time and prevents proper page rendering.
This comprehensive guide explains what causes this error, why it happens, and provides multiple solutions to fix it in your Next.js projects with clean code examples and directory structure.
What is the window is not defined Error?
The “window is not defined” error occurs when:
- Accessing
windowobject during server-side rendering - Using browser-specific APIs in server components
- Calling
windowmethods during initial render - Using browser APIs in useEffect without proper checks
- Importing libraries that use browser APIs at module level
- Accessing
documentornavigatorobjects on the server
Common Error Messages:
ReferenceError: window is not definedReferenceError: document is not definedReferenceError: localStorage is not definedCannot read properties of undefined (reading 'localStorage')TypeError: Cannot read property 'matchMedia' of undefined
Understanding the Problem
Next.js performs server-side rendering (SSR) by default, meaning components are initially rendered on the server where browser APIs like window don’t exist. The error occurs when code tries to access these APIs during the server render phase. This is a fundamental difference between client-side and server-side JavaScript environments, where the server lacks browser-specific objects and methods.
Typical Next.js Project Structure:
my-next-app/
├── package.json
├── next.config.js
├── app/
│ ├── layout.js
│ ├── page.js
│ ├── globals.css
│ ├── components/
│ │ ├── Header.jsx
│ │ ├── WindowComponent.jsx
│ │ └── DynamicComponent.jsx
│ └── api/
│ └── route.js
├── lib/
│ └── utils.js
├── hooks/
│ └── useWindow.js
└── public/
Solution 1: Check if Window Exists Before Using It
The most common solution is to check if the window object exists before accessing it.
❌ Without Window Check:
// components/BrowserFeature.jsx - ❌ Accessing window without check
export default function BrowserFeature() {
// ❌ This will cause "window is not defined" error
const isMobile = window.innerWidth < 768;
return (
<div>
<p>Screen width: {window.innerWidth}px</p>
<p>Is mobile: {isMobile ? 'Yes' : 'No'}</p>
</div>
);
}
✅ With Window Check:
components/BrowserFeature.jsx:
'use client'; // ✅ Mark as client component
export default function BrowserFeature() {
// ✅ Check if window exists before accessing
const screenWidth = typeof window !== 'undefined' ? window.innerWidth : 0;
const isMobile = screenWidth < 768;
return (
<div>
<p>Screen width: {screenWidth}px</p>
<p>Is mobile: {isMobile ? 'Yes' : 'No'}</p>
</div>
);
}
lib/window-utils.js:
// ✅ Utility functions that safely access window
export function getWindowDimensions() {
if (typeof window !== 'undefined') {
return {
width: window.innerWidth,
height: window.innerHeight
};
}
return { width: 0, height: 0 }; // Default values for server
}
export function isClientSide() {
return typeof window !== 'undefined';
}
export function getLocalStorage(key, defaultValue = null) {
if (typeof window !== 'undefined' && window.localStorage) {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : defaultValue;
} catch (error) {
console.error('Error reading from localStorage:', error);
return defaultValue;
}
}
return defaultValue;
}
export function setLocalStorage(key, value) {
if (typeof window !== 'undefined' && window.localStorage) {
try {
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error('Error writing to localStorage:', error);
}
}
}
export function checkMediaQuery(query) {
if (typeof window !== 'undefined' && window.matchMedia) {
return window.matchMedia(query).matches;
}
return false; // Default to false on server
}
Solution 2: Use useEffect for Browser-Specific Code
Move browser-specific code to useEffect, which only runs on the client side.
components/ResponsiveComponent.jsx:
'use client'; // ✅ Client component
import { useState, useEffect } from 'react';
import { getWindowDimensions, checkMediaQuery } from '@/lib/window-utils';
export default function ResponsiveComponent() {
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
const [isMobile, setIsMobile] = useState(false);
const [isDarkMode, setIsDarkMode] = useState(false);
useEffect(() => {
// ✅ Browser-specific code runs only on client
const updateDimensions = () => {
const dims = getWindowDimensions();
setDimensions(dims);
setIsMobile(dims.width < 768);
};
// ✅ Initial update
updateDimensions();
// ✅ Add resize listener
window.addEventListener('resize', updateDimensions);
// ✅ Check for dark mode preference
const darkModeMediaQuery = checkMediaQuery('(prefers-color-scheme: dark)');
setIsDarkMode(darkModeMediaQuery);
// ✅ Cleanup
return () => {
window.removeEventListener('resize', updateDimensions);
};
}, []);
return (
<div className="p-4">
<h2>Responsive Component</h2>
<p>Width: {dimensions.width}px</p>
<p>Height: {dimensions.height}px</p>
<p>Is Mobile: {isMobile ? 'Yes' : 'No'}</p>
<p>Dark Mode: {isDarkMode ? 'Enabled' : 'Disabled'}</p>
</div>
);
}
Solution 3: Use Dynamic Imports with noSSR
Use Next.js dynamic imports to load components that use browser APIs only on the client side.
components/DynamicWindowComponent.jsx:
'use client'; // ✅ Client component
import { useState, useEffect } from 'react';
export default function DynamicWindowComponent() {
const [windowInfo, setWindowInfo] = useState(null);
useEffect(() => {
// ✅ This runs only on the client
setWindowInfo({
width: window.innerWidth,
height: window.innerHeight,
userAgent: navigator.userAgent,
language: navigator.language,
online: navigator.onLine
});
}, []);
if (!windowInfo) {
return <div>Loading...</div>;
}
return (
<div className="p-4 bg-blue-100">
<h3>Window Information</h3>
<p>Width: {windowInfo.width}px</p>
<p>Height: {windowInfo.height}px</p>
<p>User Agent: {windowInfo.userAgent}</p>
<p>Language: {windowInfo.language}</p>
<p>Online: {windowInfo.online ? 'Yes' : 'No'}</p>
</div>
);
}
pages/index.js:
import dynamic from 'next/dynamic';
// ✅ Dynamically import component with no SSR
const DynamicWindowComponent = dynamic(
() => import('@/components/DynamicWindowComponent'),
{
ssr: false, // ✅ Disable server-side rendering for this component
loading: () => <p>Loading window component...</p> // ✅ Loading component
}
);
export default function HomePage() {
return (
<div>
<h1>Home Page</h1>
<p>This content is server-rendered.</p>
{/* ✅ This component will only render on the client */}
<DynamicWindowComponent />
</div>
);
}
Solution 4: Create a Custom Hook for Window Features
Create a custom hook that safely handles browser-specific features.
hooks/useWindow.js:
'use client'; // ✅ This hook must be in a client component
import { useState, useEffect } from 'react';
export function useWindow() {
const [windowData, setWindowData] = useState({
width: 0,
height: 0,
isClient: false,
isMobile: false,
isTablet: false,
isDesktop: false
});
useEffect(() => {
// ✅ Set isClient to true when component mounts on client
setWindowData(prev => ({ ...prev, isClient: true }));
const updateWindowData = () => {
const width = window.innerWidth;
const height = window.innerHeight;
setWindowData({
width,
height,
isClient: true,
isMobile: width < 768,
isTablet: width >= 768 && width < 1024,
isDesktop: width >= 1024
});
};
// ✅ Initial update
updateWindowData();
// ✅ Add event listener
window.addEventListener('resize', updateWindowData);
// ✅ Cleanup
return () => {
window.removeEventListener('resize', updateWindowData);
};
}, []);
return windowData;
}
export function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(initialValue);
useEffect(() => {
if (typeof window !== 'undefined') {
try {
const item = window.localStorage.getItem(key);
setStoredValue(item ? JSON.parse(item) : initialValue);
} catch (error) {
console.error('Error reading from localStorage:', error);
}
}
}, [key, initialValue]);
const setValue = (value) => {
try {
setStoredValue(value);
if (typeof window !== 'undefined') {
window.localStorage.setItem(key, JSON.stringify(value));
}
} catch (error) {
console.error('Error writing to localStorage:', error);
}
};
return [storedValue, setValue];
}
export function useMediaQuery(query) {
const [matches, setMatches] = useState(false);
useEffect(() => {
if (typeof window !== 'undefined') {
const media = window.matchMedia(query);
if (media.matches !== matches) {
setMatches(media.matches);
}
const listener = () => setMatches(media.matches);
media.addEventListener('change', listener);
return () => media.removeEventListener('change', listener);
}
}, [matches, query]);
return matches;
}
components/HookBasedComponent.jsx:
'use client'; // ✅ Client component using custom hooks
import { useWindow, useMediaQuery, useLocalStorage } from '@/hooks/useWindow';
export default function HookBasedComponent() {
const { width, height, isMobile, isTablet, isDesktop } = useWindow();
const isDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
const [theme, setTheme] = useLocalStorage('theme', 'light');
const handleThemeChange = () => {
const newTheme = theme === 'light' ? 'dark' : 'light';
setTheme(newTheme);
};
if (!isClient) {
return <div>Loading...</div>;
}
return (
<div className="p-4">
<h2>Hook-Based Component</h2>
<div className="mb-4">
<p>Width: {width}px</p>
<p>Height: {height}px</p>
<p>Device: {isMobile ? 'Mobile' : isTablet ? 'Tablet' : 'Desktop'}</p>
<p>Dark Mode Preferred: {isDarkMode ? 'Yes' : 'No'}</p>
<p>Current Theme: {theme}</p>
</div>
<div className="space-x-2">
<button
onClick={handleThemeChange}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
Toggle Theme
</button>
</div>
</div>
);
}
Solution 5: Use typeof Checks Throughout Your Code
Implement typeof checks wherever browser APIs are accessed.
lib/storage-manager.js:
// ✅ Safe storage manager with typeof checks
export class StorageManager {
static getItem(key, defaultValue = null) {
// ✅ Check if localStorage exists
if (typeof window !== 'undefined' && window.localStorage) {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : defaultValue;
} catch (error) {
console.error('Error reading from localStorage:', error);
return defaultValue;
}
}
return defaultValue;
}
static setItem(key, value) {
// ✅ Check if localStorage exists
if (typeof window !== 'undefined' && window.localStorage) {
try {
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error('Error writing to localStorage:', error);
}
}
}
static removeItem(key) {
// ✅ Check if localStorage exists
if (typeof window !== 'undefined' && window.localStorage) {
try {
window.localStorage.removeItem(key);
} catch (error) {
console.error('Error removing from localStorage:', error);
}
}
}
static clear() {
// ✅ Check if localStorage exists
if (typeof window !== 'undefined' && window.localStorage) {
try {
window.localStorage.clear();
} catch (error) {
console.error('Error clearing localStorage:', error);
}
}
}
// ✅ Check if storage is available
static isStorageAvailable() {
if (typeof window === 'undefined') {
return false;
}
try {
const test = '__storage_test__';
window.localStorage.setItem(test, test);
window.localStorage.removeItem(test);
return true;
} catch (error) {
return false;
}
}
}
// ✅ Safe window utilities
export function getWindowProperty(property, defaultValue = null) {
if (typeof window !== 'undefined') {
return window[property] || defaultValue;
}
return defaultValue;
}
export function getNavigatorProperty(property, defaultValue = null) {
if (typeof navigator !== 'undefined') {
return navigator[property] || defaultValue;
}
return defaultValue;
}
export function getDocumentProperty(property, defaultValue = null) {
if (typeof document !== 'undefined') {
return document[property] || defaultValue;
}
return defaultValue;
}
Solution 6: Handle Third-Party Libraries Safely
Properly handle third-party libraries that use browser APIs.
components/ThirdPartyWrapper.jsx:
'use client'; // ✅ Client component for third-party libraries
import { useState, useEffect } from 'react';
import dynamic from 'next/dynamic';
// ✅ Dynamically import libraries that use browser APIs
const ChartComponent = dynamic(() => import('./ChartComponent'), {
ssr: false,
loading: () => <div>Loading chart...</div>
});
const MapComponent = dynamic(() => import('./MapComponent'), {
ssr: false,
loading: () => <div>Loading map...</div>
});
export default function ThirdPartyWrapper() {
const [isClient, setIsClient] = useState(false);
useEffect(() => {
// ✅ Set isClient to true when component mounts
setIsClient(true);
}, []);
return (
<div>
<h2>Third-Party Components</h2>
{/* ✅ Only render when client-side */}
{isClient && (
<>
<ChartComponent />
<MapComponent />
</>
)}
</div>
);
}
components/ChartComponent.jsx:
'use client'; // ✅ Client component for chart library
import { useEffect, useRef } from 'react';
export default function ChartComponent() {
const canvasRef = useRef(null);
useEffect(() => {
if (typeof window !== 'undefined' && canvasRef.current) {
// ✅ Safe to use canvas API here
const canvas = canvasRef.current;
const ctx = canvas.getContext('2d');
// ✅ Draw a simple chart
ctx.fillStyle = '#4F46E5';
ctx.fillRect(10, 10, 100, 50);
}
}, []);
return (
<div className="mt-4">
<canvas
ref={canvasRef}
width={200}
height={100}
className="border border-gray-300"
/>
</div>
);
}
Solution 7: Use Next.js Built-in Features
Leverage Next.js built-in features to handle client-side code properly.
components/ClientOnly.jsx:
'use client'; // ✅ Client component
import { useState, useEffect } from 'react';
export default function ClientOnly({ children, fallback = null }) {
const [isClient, setIsClient] = useState(false);
useEffect(() => {
setIsClient(true);
}, []);
return isClient ? children : fallback;
}
components/WindowAwareComponent.jsx:
import ClientOnly from './ClientOnly';
export default function WindowAwareComponent() {
return (
<div>
<h2>Server-Side Content</h2>
<p>This content is rendered on the server.</p>
{/* ✅ Client-only content */}
<ClientOnly fallback={<div>Loading client content...</div>}>
<div className="mt-4 p-4 bg-green-100">
<h3>Client-Side Content</h3>
<p>Window width: {typeof window !== 'undefined' ? window.innerWidth : 'N/A'}</p>
</div>
</ClientOnly>
</div>
);
}
Working Code Examples
Complete Next.js App with Safe Window Usage:
// app/layout.js
import './globals.css';
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className="min-h-screen bg-gray-100">
<header className="bg-blue-600 text-white p-4">
<h1>Next.js Window Safety Demo</h1>
</header>
<main className="container mx-auto p-4">
{children}
</main>
<footer className="mt-8 p-4 bg-gray-800 text-white text-center">
<p>© 2026 Safe Window Usage Demo</p>
</footer>
</body>
</html>
);
}
Home Page:
// app/page.js
import WindowAwareComponent from '@/components/WindowAwareComponent';
import ResponsiveComponent from '@/components/ResponsiveComponent';
import DynamicWindowComponent from '@/components/DynamicWindowComponent';
import HookBasedComponent from '@/components/HookBasedComponent';
export default function HomePage() {
return (
<div>
<h1 className="text-3xl font-bold mb-6">Safe Window Usage Examples</h1>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="bg-white p-4 rounded-lg shadow-md">
<h2 className="text-xl font-semibold mb-4">Server-Side Content</h2>
<p>This section is rendered on the server and doesn't access window.</p>
</div>
<div className="bg-white p-4 rounded-lg shadow-md">
<h2 className="text-xl font-semibold mb-4">Client-Only Content</h2>
<WindowAwareComponent />
</div>
<div className="bg-white p-4 rounded-lg shadow-md">
<h2 className="text-xl font-semibold mb-4">Responsive Component</h2>
<ResponsiveComponent />
</div>
<div className="bg-white p-4 rounded-lg shadow-md">
<h2 className="text-xl font-semibold mb-4">Hook-Based Component</h2>
<HookBasedComponent />
</div>
</div>
<div className="mt-6 bg-white p-4 rounded-lg shadow-md">
<h2 className="text-xl font-semibold mb-4">Dynamic Component</h2>
<DynamicWindowComponent />
</div>
</div>
);
}
Safe Utility Component:
// components/SafeUtils.jsx
'use client'; // ✅ Client component
import { useState, useEffect } from 'react';
import { StorageManager, getWindowProperty, getNavigatorProperty } from '@/lib/storage-manager';
export default function SafeUtils() {
const [browserInfo, setBrowserInfo] = useState({
width: 0,
height: 0,
userAgent: '',
language: '',
online: false
});
useEffect(() => {
if (typeof window !== 'undefined') {
setBrowserInfo({
width: window.innerWidth,
height: window.innerHeight,
userAgent: navigator.userAgent,
language: navigator.language,
online: navigator.onLine
});
}
}, []);
const handleStorage = () => {
if (StorageManager.isStorageAvailable()) {
StorageManager.setItem('lastVisit', new Date().toISOString());
alert('Last visit stored!');
} else {
alert('Storage not available');
}
};
return (
<div className="p-4 bg-purple-100 rounded">
<h3>Safe Browser Utilities</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
<div>
<h4>Window Info</h4>
<p>Width: {browserInfo.width}px</p>
<p>Height: {browserInfo.height}px</p>
<p>Online: {browserInfo.online ? 'Yes' : 'No'}</p>
</div>
<div>
<h4>Navigator Info</h4>
<p>User Agent: {browserInfo.userAgent}</p>
<p>Language: {browserInfo.language}</p>
</div>
</div>
<button
onClick={handleStorage}
className="mt-4 px-4 py-2 bg-purple-500 text-white rounded hover:bg-purple-600"
>
Store Visit Time
</button>
</div>
);
}
Best Practices for Window Safety
1. Always Check typeof window
// ✅ Always check typeof window
if (typeof window !== 'undefined') {
// Safe to use window APIs
const width = window.innerWidth;
}
2. Use useEffect for Browser APIs
// ✅ Use useEffect for browser-specific code
useEffect(() => {
// This runs only on the client
}, []);
3. Leverage Dynamic Imports
// ✅ Use dynamic imports for components using browser APIs
const Component = dynamic(() => import('./Component'), { ssr: false });
4. Create Safe Utility Functions
// ✅ Create utility functions that handle window safely
export function getWindowWidth() {
return typeof window !== 'undefined' ? window.innerWidth : 0;
}
5. Use Custom Hooks
// ✅ Use custom hooks for window-related logic
const { width, height } = useWindow();
Debugging Steps
Step 1: Identify the Problematic Code
# Look for direct window usage in your components
grep -r "window\." src/
Step 2: Check Component Types
# Verify client components have 'use client' directive
grep -r "'use client'" src/
Step 3: Test Server-Side Rendering
# Build the application to test SSR
npm run build
Step 4: Use typeof Checks
// Add typeof checks around window usage
if (typeof window !== 'undefined') {
// Your window code here
}
Step 5: Implement Dynamic Imports
// For components that must use browser APIs
const Component = dynamic(() => import('./Component'), { ssr: false });
Common Mistakes to Avoid
1. Direct Window Access
// ❌ Don't access window directly
const width = window.innerWidth; // This will cause errors
2. Browser APIs in Server Components
// ❌ Don't use browser APIs in server components
export default function ServerComponent() {
const width = window.innerWidth; // Error!
return <div>{width}</div>;
}
3. Module-Level Browser API Usage
// ❌ Don't use browser APIs at module level
const isMobile = window.innerWidth < 768; // Error!
4. Forgetting ‘use client’ Directive
// ❌ Missing 'use client' directive
import { useState } from 'react';
export default function Component() {
const width = window.innerWidth; // Error without 'use client'
return <div>{width}</div>;
}
Performance Considerations
1. Minimize Client-Side Code
// ✅ Only make components client components when necessary
// ✅ Use server components for static content
2. Optimize Dynamic Imports
// ✅ Use dynamic imports wisely
const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
loading: () => <div>Loading...</div>
});
3. Efficient Resize Handlers
// ✅ Use throttling/debouncing for resize handlers
import { throttle } from 'lodash';
const handleResize = throttle(() => {
// Handle resize
}, 100);
Security Considerations
1. Validate Browser APIs
// ✅ Always validate browser APIs exist
if (typeof window !== 'undefined' && window.localStorage) {
// Safe to use localStorage
}
2. Handle Storage Failures
// ✅ Handle storage errors gracefully
try {
window.localStorage.setItem('key', 'value');
} catch (error) {
console.error('Storage failed:', error);
}
3. Sanitize User Input
// ✅ Sanitize any data stored in browser APIs
const sanitizedData = sanitize(userInput);
window.localStorage.setItem('data', JSON.stringify(sanitizedData));
Testing Client-Side Code
1. Unit Test Client Components
// Using React Testing Library
import { render, screen } from '@testing-library/react';
import { useWindow } from '@/hooks/useWindow';
import YourComponent from './YourComponent';
// Mock window properties
Object.defineProperty(window, 'innerWidth', {
writable: true,
configurable: true,
value: 1024,
});
describe('YourComponent', () => {
test('renders without crashing', () => {
render(<YourComponent />);
expect(screen.getByText(/content/i)).toBeInTheDocument();
});
});
2. Test typeof Checks
test('handles server-side rendering', () => {
// Simulate server-side environment
const originalWindow = global.window;
delete global.window;
expect(() => {
render(<YourComponent />);
}).not.toThrow();
global.window = originalWindow;
});
Alternative Solutions
1. Use Next.js Built-in Features
// ✅ Use Next.js Image component instead of direct DOM manipulation
import Image from 'next/image';
<Image src="/image.jpg" alt="Description" width={300} height={200} />;
2. Server-Side Feature Detection
// ✅ Use user-agent parsing on the server for basic detection
import { headers } from 'next/headers';
export default function ServerComponent() {
const headersList = headers();
const userAgent = headersList.get('user-agent');
// Parse user agent for basic feature detection
}
3. Progressive Enhancement
// ✅ Start with server-rendered content and enhance on the client
export default function ProgressiveComponent() {
return (
<div>
<noscript>
<p>JavaScript is required for full functionality</p>
</noscript>
<ClientEnhancement />
</div>
);
}
Migration Checklist
- Add typeof checks around all window usage
- Convert components using browser APIs to client components with ‘use client’
- Use dynamic imports for components requiring browser APIs
- Move browser-specific code to useEffect hooks
- Create safe utility functions for window access
- Test server-side rendering compatibility
- Verify all third-party libraries are handled safely
- Update error boundaries to handle client component errors
Conclusion
The ‘window is not defined’ error is a common challenge in Next.js development that stems from the fundamental difference between server-side and client-side environments. By following the solutions provided in this guide—implementing typeof checks, using useEffect for browser APIs, leveraging dynamic imports, creating custom hooks, and properly structuring your components—you can effectively resolve this error and build robust Next.js applications.
The key is to understand when and where browser APIs can be safely accessed, implement proper safeguards, and structure your code to work seamlessly in both server and client environments. With proper window safety measures, your Next.js applications will handle browser-specific features reliably while maintaining optimal performance and user experience.
Remember to test your implementation thoroughly in both development and production environments, follow Next.js best practices for component separation, implement proper error handling, and stay updated with Next.js documentation to ensure your applications maintain the best possible architecture for handling browser APIs safely.
Related Articles
Fix: document is not defined in Next.js Error - Complete Client-Side Guide
Complete guide to fix 'document is not defined' error in Next.js applications. Learn how to handle browser APIs safely in server-side rendering environments.
Fix: useRouter only works in Client Components Error
Learn how to fix the 'useRouter only works in Client Components' error in Next.js applications. This comprehensive guide covers client components, server components, and proper routing implementation.
Fix: cookies() can only be used in Server Components error Next.js
Quick fix for 'cookies() can only be used in Server Components' error in Next.js. Learn how to properly use cookies in Server Components.