search
next star Featured

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.

person By Gautam Sharma
calendar_today January 8, 2026
schedule 13 min read
Next.js Blank Page Build Error Production Deployment Troubleshooting SSR

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.

Gautam Sharma

About Gautam Sharma

Full-stack developer and tech blogger sharing coding tutorials and best practices

Related Articles

next

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.

January 8, 2026
next

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.

January 8, 2026
next

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.

January 8, 2026