No articles found
Try different keywords or browse our categories
Fix: Next.js app shows blank page after build error - Complete Build Troubleshooting Guide
Complete guide to fix 'Next.js app shows blank page after build' errors. Learn how to troubleshoot production builds and resolve common deployment issues.
The ‘Next.js app shows blank page after build’ error is a common issue that occurs when your Next.js application builds successfully but displays a blank page in production. This error typically happens due to JavaScript errors, missing dependencies, incorrect asset paths, or server-side rendering issues. Understanding and resolving this error is crucial for successful Next.js deployments.
Understanding the Problem
When a Next.js app shows a blank page after build, it’s usually due to:
- JavaScript errors preventing React from rendering
- Missing or incorrectly bundled assets
- Server-side rendering failures
- Environment-specific code issues
- Third-party library compatibility problems
- Asset path configuration errors
- Missing polyfills for older browsers
Solution 1: Check Browser Console for Errors
The first step is to identify the underlying JavaScript error causing the blank page.
Common Console Errors:
// ❌ Example console errors that cause blank pages:
// Uncaught ReferenceError: React is not defined
// Uncaught TypeError: Cannot read property 'map' of undefined
// Uncaught SyntaxError: Unexpected token '<'
// Uncaught Error: Minified React error #130
Check Console in Production:
# Open browser developer tools (F12) and check Console tab
# Look for JavaScript errors that prevent rendering
Solution 2: Proper Error Handling and Logging
Implement proper error handling to catch and log issues that cause blank pages.
pages/_app.js:
import '../styles/globals.css';
import { useState, useEffect } from 'react';
// ✅ Error boundary component
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null, errorInfo: null };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error('Error caught by boundary:', error, errorInfo);
this.setState({
error: error,
errorInfo: errorInfo
});
}
render() {
if (this.state.hasError) {
return (
<div className="error-container">
<h2>Something went wrong.</h2>
<details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.error && this.state.error.toString()}
<br />
{this.state.errorInfo.componentStack}
</details>
</div>
);
}
return this.props.children;
}
}
function MyApp({ Component, pageProps }) {
const [isMounted, setIsMounted] = useState(false);
useEffect(() => {
// ✅ Mark as mounted to handle client-side only components
setIsMounted(true);
}, []);
return (
<ErrorBoundary>
{isMounted ? <Component {...pageProps} /> : <div>Loading...</div>}
</ErrorBoundary>
);
}
export default MyApp;
components/GlobalErrorBoundary.jsx:
import { Component } from 'react';
class GlobalErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('Global error caught:', error, errorInfo);
// ✅ Log error to external service
if (typeof window !== 'undefined') {
fetch('/api/log-error', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
error: error.toString(),
stack: error.stack,
url: window.location.href,
timestamp: new Date().toISOString(),
}),
}).catch(console.error);
}
}
render() {
if (this.state.hasError) {
return (
<div className="global-error">
<h1>Application Error</h1>
<p>We're sorry, but something went wrong.</p>
<button onClick={() => window.location.reload()}>
Reload Page
</button>
</div>
);
}
return this.props.children;
}
}
export default GlobalErrorBoundary;
Solution 3: Handle Client-Side Only Components
Many blank page issues occur when browser-specific code runs during server-side rendering.
components/BrowserOnlyComponent.jsx:
import { useState, useEffect } from 'react';
const BrowserOnlyComponent = ({ children }) => {
const [isClient, setIsClient] = useState(false);
useEffect(() => {
// ✅ Only set to client after mounting
setIsClient(true);
}, []);
return (
<div>
{isClient ? (
children
) : (
<div className="skeleton-loader">
<p>Loading...</p>
</div>
)}
</div>
);
};
export default BrowserOnlyComponent;
components/WindowDimensionComponent.jsx:
import { useState, useEffect } from 'react';
const WindowDimensionComponent = () => {
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
useEffect(() => {
// ✅ Only run on client
if (typeof window !== 'undefined') {
const updateDimensions = () => {
setDimensions({
width: window.innerWidth,
height: window.innerHeight,
});
};
updateDimensions();
window.addEventListener('resize', updateDimensions);
return () => window.removeEventListener('resize', updateDimensions);
}
}, []);
return (
<div>
<p>Width: {dimensions.width}</p>
<p>Height: {dimensions.height}</p>
</div>
);
};
export default WindowDimensionComponent;
Solution 4: Proper Dynamic Imports
Use dynamic imports for components that cause build issues.
components/DynamicComponent.jsx:
import { useState } from 'react';
import dynamic from 'next/dynamic';
// ✅ Dynamically import problematic components
const ChartComponent = dynamic(
() => import('./ChartComponent'),
{
ssr: false, // ✅ Skip server-side rendering
loading: () => <div>Loading chart...</div>
}
);
const MapComponent = dynamic(
() => import('./MapComponent'),
{
ssr: false,
loading: () => <div>Loading map...</div>
}
);
const DynamicPage = () => {
const [showChart, setShowChart] = useState(false);
const [showMap, setShowMap] = useState(false);
return (
<div>
<button onClick={() => setShowChart(!showChart)}>
{showChart ? 'Hide' : 'Show'} Chart
</button>
<button onClick={() => setShowMap(!showMap)}>
{showMap ? 'Hide' : 'Show'} Map
</button>
{showChart && <ChartComponent />}
{showMap && <MapComponent />}
</div>
);
};
export default DynamicPage;
Solution 5: Environment-Specific Code
Handle environment-specific code properly to avoid build issues.
utils/envUtils.js:
// ✅ Environment utility functions
export const isServer = () => typeof window === 'undefined';
export const isClient = () => typeof window !== 'undefined';
export const safeWindow = () => {
if (isClient()) {
return window;
}
return {
innerWidth: 0,
innerHeight: 0,
localStorage: {
getItem: () => null,
setItem: () => {},
removeItem: () => {},
},
};
};
export const safeDocument = () => {
if (isClient()) {
return document;
}
return {
getElementById: () => null,
querySelector: () => null,
createElement: () => ({ style: {} }),
};
};
// ✅ Safe localStorage operations
export const safeLocalStorage = {
getItem: (key) => {
if (isClient() && window.localStorage) {
try {
return window.localStorage.getItem(key);
} catch (error) {
console.error('localStorage get error:', error);
return null;
}
}
return null;
},
setItem: (key, value) => {
if (isClient() && window.localStorage) {
try {
window.localStorage.setItem(key, value);
} catch (error) {
console.error('localStorage set error:', error);
}
}
},
};
components/EnvSafeComponent.jsx:
import { useState, useEffect } from 'react';
import { isClient, safeLocalStorage } from '../utils/envUtils';
const EnvSafeComponent = () => {
const [userPref, setUserPref] = useState('default');
useEffect(() => {
if (isClient()) {
// ✅ Safe access to browser APIs
const savedPref = safeLocalStorage.getItem('userPreference');
if (savedPref) {
setUserPref(savedPref);
}
}
}, []);
const handleSave = () => {
if (isClient()) {
safeLocalStorage.setItem('userPreference', userPref);
}
};
return (
<div>
<input
type="text"
value={userPref}
onChange={(e) => setUserPref(e.target.value)}
placeholder="Enter preference"
/>
<button onClick={handleSave}>Save</button>
</div>
);
};
export default EnvSafeComponent;
Solution 6: Asset Path Configuration
Fix asset path issues that cause blank pages in production.
next.config.js:
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
// ✅ Configure asset prefix for production
assetPrefix: process.env.NODE_ENV === 'production' ? '/your-app-prefix' : '',
// ✅ Handle static asset paths
images: {
domains: ['example.com', 'cdn.example.com'],
path: '/_next/image',
},
// ✅ Configure basePath if deploying to subdirectory
basePath: process.env.NODE_ENV === 'production' ? '/subdirectory' : '',
// ✅ Custom webpack configuration
webpack: (config, { isServer }) => {
if (!isServer) {
// ✅ Polyfill for Node.js globals in browser
config.resolve.fallback = {
...config.resolve.fallback,
fs: false,
net: false,
tls: false,
};
}
return config;
},
// ✅ Environment-specific configuration
env: {
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000/api',
},
};
module.exports = nextConfig;
components/AssetComponent.jsx:
import Image from 'next/image';
import { useState } from 'react';
const AssetComponent = () => {
const [imageLoaded, setImageLoaded] = useState(false);
return (
<div>
<Image
src="/images/logo.png" // ✅ Relative path from public folder
alt="Logo"
width={200}
height={100}
onLoadingComplete={() => setImageLoaded(true)}
onError={() => console.error('Image failed to load')}
/>
{!imageLoaded && <div>Loading image...</div>}
<img
src={`${process.env.NEXT_PUBLIC_BASE_PATH || ''}/images/banner.jpg`}
alt="Banner"
onError={() => console.error('Banner image failed to load')}
/>
</div>
);
};
export default AssetComponent;
Solution 7: Build Configuration and Optimization
Optimize your build configuration to prevent blank page issues.
package.json:
{
"name": "my-next-app",
"version": "1.0.0",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"analyze": "ANALYZE=true next build",
"test:build": "next build && next start"
},
"dependencies": {
"next": "^14.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"@types/react": "^18.0.0",
"eslint": "^8.0.0",
"eslint-config-next": "^14.0.0"
}
}
pages/_document.js:
import { Html, Head, Main, NextScript } from 'next/document';
export default function Document() {
return (
<Html lang="en">
<Head>
{/* ✅ Preload critical fonts and assets */}
<link rel="preload" href="/fonts/main-font.woff2" as="font" type="font/woff2" crossOrigin="anonymous" />
{/* ✅ Critical CSS */}
<style jsx global>{`
body { margin: 0; padding: 0; }
.skeleton { background: #f0f0f0; }
`}</style>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
Working Code Examples
Complete Production-Ready Page:
// pages/index.jsx
import { useState, useEffect } from 'react';
import dynamic from 'next/dynamic';
import Head from 'next/head';
// ✅ Dynamically import components that might cause issues
const ChartComponent = dynamic(() => import('../components/ChartComponent'), {
ssr: false,
loading: () => <div className="loading">Loading chart...</div>
});
const HomePage = () => {
const [isClient, setIsClient] = useState(false);
const [data, setData] = useState(null);
useEffect(() => {
// ✅ Mark as client after mounting
setIsClient(true);
// ✅ Fetch data after mount
const fetchData = async () => {
try {
const response = await fetch('/api/data');
const result = await response.json();
setData(result);
} catch (error) {
console.error('Error fetching data:', error);
}
};
fetchData();
}, []);
return (
<>
<Head>
<title>Production Ready App</title>
<meta name="description" content="Next.js app ready for production" />
</Head>
<div className="container">
<header>
<h1>Welcome to Production</h1>
</header>
<main>
<section>
<h2>Server-Side Content</h2>
<p>This content renders on the server and will hydrate properly.</p>
</section>
<section>
<h2>Client-Side Content</h2>
{isClient ? (
<div>
<p>Client-side content loaded successfully.</p>
{data && <p>Data: {JSON.stringify(data)}</p>}
</div>
) : (
<p>Loading client content...</p>
)}
</section>
<section>
<h2>Dynamic Component</h2>
<ChartComponent />
</section>
</main>
<footer>
<p>Footer content that renders on both server and client.</p>
</footer>
</div>
</>
);
};
export default HomePage;
Build Script with Error Handling:
#!/bin/bash
# build.sh
echo "Starting Next.js build process..."
# ✅ Clean previous builds
rm -rf .next
# ✅ Install dependencies
npm ci
# ✅ Run build with error handling
if npm run build; then
echo "Build successful!"
# ✅ Test the build
echo "Testing build..."
timeout 30 npm start &
START_PID=$!
# ✅ Wait a moment for server to start
sleep 5
# ✅ Test if server responds
if curl -f http://localhost:3000 > /dev/null 2>&1; then
echo "Build test passed!"
kill $START_PID
else
echo "Build test failed - server not responding"
kill $START_PID
exit 1
fi
else
echo "Build failed!"
exit 1
fi
echo "Build process completed successfully!"
Debugging Steps
Step 1: Check Build Output
# ✅ Run build and check for errors
npm run build
# Look for any error messages in the output
Step 2: Test Production Build Locally
# ✅ Start production server
npm run start
# ✅ Open browser to http://localhost:3000
# Check browser console for errors
Step 3: Check Browser Console
// ✅ Open Developer Tools (F12)
// Look for JavaScript errors
// Check Network tab for failed asset loads
Step 4: Verify Environment Variables
# ✅ Check environment variables
echo $NODE_ENV
echo $NEXT_PUBLIC_API_URL
# Ensure all required env vars are set
Step 5: Test Individual Pages
# ✅ Test specific pages
curl http://localhost:3000/
curl http://localhost:3000/api/health
# Check if basic requests work
Common Mistakes to Avoid
1. Direct Browser API Access
// ❌ Don't do this
const Component = () => {
const width = window.innerWidth; // ❌ Error during SSR
return <div>{width}</div>;
};
2. Forgetting Dynamic Imports
// ❌ Don't import browser-only libraries directly
import Chart from 'chart.js'; // ❌ May cause build issues
3. Incorrect Asset Paths
// ❌ Don't use absolute paths that don't exist
<img src="/absolute/path/to/image.jpg" /> // ❌ May fail in production
4. Missing Error Boundaries
// ❌ Don't skip error handling
// Always implement error boundaries for production
5. Environment-Specific Code
// ❌ Don't run browser code during SSR
if (process.browser) { // ❌ Old Next.js pattern
// browser code
}
Performance Considerations
1. Optimize Bundle Size
// ✅ Use dynamic imports for heavy components
const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
loading: () => <SkeletonLoader />
});
2. Lazy Load Non-Critical Resources
// ✅ Lazy load non-critical components
const Sidebar = dynamic(() => import('./Sidebar'), { ssr: false });
3. Optimize Images
// ✅ Use Next.js Image component
import Image from 'next/image';
<Image src="/path/to/image.jpg" alt="Description" width={300} height={200} />;
Security Considerations
1. Validate Environment Variables
// ✅ Validate required environment variables
if (!process.env.NEXT_PUBLIC_API_URL) {
throw new Error('NEXT_PUBLIC_API_URL is required');
}
2. Sanitize Dynamic Content
// ✅ Sanitize content before rendering
const safeContent = DOMPurify.sanitize(userContent);
3. Secure API Calls
// ✅ Use secure API calls
const response = await fetch('/api/secure-endpoint', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
});
Testing Production Builds
1. Test Build Process
# ✅ Test build process
npm run build
npm start
# Verify app loads correctly
2. Test Different Environments
# ✅ Test in different environments
NODE_ENV=production npm run build
npm start
3. Test Error Scenarios
// ✅ Test error boundaries
// Intentionally cause errors to verify error handling
Alternative Solutions
1. Use Next.js Analytics
// ✅ Enable Next.js analytics for production insights
// next.config.js
const nextConfig = {
experimental: {
instrumentationHook: true,
},
};
2. Implement Health Checks
// ✅ Add health check endpoint
// pages/api/health.js
export default function handler(req, res) {
res.status(200).json({ status: 'healthy', timestamp: new Date().toISOString() });
}
3. Use Monitoring Tools
// ✅ Integrate with monitoring services
// Log errors to external services
Migration Checklist
- Check browser console for JavaScript errors
- Implement error boundaries
- Handle client-side only components properly
- Use dynamic imports for problematic components
- Verify asset paths and configurations
- Test build process thoroughly
- Validate environment variables
- Check for direct browser API access
- Test in production-like environment
- Implement proper error logging
Conclusion
The ‘Next.js app shows blank page after build’ error is typically caused by JavaScript errors, server-side rendering issues, or environment-specific code problems. By following the solutions provided in this guide—implementing proper error handling, using dynamic imports, handling client-side components safely, and following best practices—you can effectively prevent and resolve this error in your Next.js applications.
The key is to understand the build process, implement proper safeguards when accessing browser APIs, use Next.js features appropriately, and maintain clean, well-organized code. With proper error handling and build configuration, your Next.js applications will deploy successfully and avoid common blank page issues.
Remember to test your changes thoroughly, follow Next.js best practices for production builds, implement proper error handling, and regularly review your build process to ensure your applications maintain the best possible architecture and avoid common deployment errors.
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: Hydration failed because the initial UI does not match error in Next.js - Complete Hydration Guide
Complete guide to fix 'Hydration failed because the initial UI does not match' error in Next.js applications. Learn how to handle client-server rendering mismatches and implement proper hydration strategies.
Fix: Images not loading in Next.js production error
Quick fix for images not loading in Next.js production. Learn how to properly configure and serve images in Next.js production builds.