How to Fix the White Screen of Death in React

Type: Software Reference Confidence: 0.93 Sources: 8 Verified: 2026-02-23 Freshness: quarterly

TL;DR

Constraints

Quick Reference

# Cause Likelihood Signature Fix
1 Unhandled render error ~30% Works in dev, blank in prod Add Error Boundary [src1]
2 Wrong homepage/base ~25% Assets return 404 in prod Set correct homepage or base [src2, src3]
3 Broken import/export ~15% "X is not a component" error Fix default/named mismatch [src5]
4 React Router on static host ~10% Root works, routes 404 on refresh SPA fallback redirects [src4]
5 Async data crash ~8% Renders then goes blank Optional chaining + loading state [src1]
6 Missing return in component ~5% No error, just blank Ensure JSX is returned [src5]
7 CORS/network error ~4% Blank + console errors Fix API CORS or proxy config [src5]
8 Minification/case error ~3% Works in dev, crashes in prod Check case-sensitivity in imports [src6]
9 ReactDOM.render() in React 19 New in 2025 App silently fails to mount Migrate to createRoot() [src7]

Decision Tree

START
├── Open DevTools Console (F12) — any errors?
│   ├── YES → Read the error message
│   │   ├── "X is not defined" → Missing import or export [src5]
│   │   ├── "Cannot read properties of undefined/null" → Data not loaded [src1]
│   │   │   └── FIX: Add optional chaining (data?.field) or loading state
│   │   ├── "Element type is invalid" → Import/export mismatch [src5]
│   │   │   └── FIX: Check default vs named exports
│   │   ├── "Loading chunk failed" → Code splitting error [src6]
│   │   │   └── FIX: Clear cache, check CDN, add Suspense fallback
│   │   ├── "createRoot is not a function" → React 19 API change [src7]
│   │   │   └── FIX: import { createRoot } from 'react-dom/client'
│   │   └── Other JS error → Fix the specific error
│   └── NO errors? → Check Network tab (F12 > Network)
│       ├── JS/CSS returning 404 → Wrong base URL [src2, src3]
│       │   └── FIX: Set homepage or base to correct path
│       ├── API calls failing → Backend issue (CORS, auth, URL)
│       └── All 200 OK → Check if root <div id="root"> exists
├── Works in dev but not prod?
│   ├── Routes work at root but 404 on refresh → Routing [src4]
│   │   └── FIX: Configure server for SPA fallback
│   ├── Blank on all routes → base URL or build issue
│   └── React 19 migration? → Check for removed APIs [src7]
└── DEFAULT → Add Error Boundary to catch and display errors [src1]

Step-by-Step Guide

1. Check the browser console (F12)

Always start here. The error that killed the render is logged. [src5]

# Common console errors that cause WSOD:
TypeError: Cannot read properties of undefined (reading 'map')
ReferenceError: X is not defined
Error: Element type is invalid: expected a string... but got undefined
ChunkLoadError: Loading chunk 5 failed

Verify: Open F12 > Console tab. If errors appear, read them before trying anything else.

2. Add an Error Boundary

Error Boundaries catch render errors and show fallback UI. [src1]

// ErrorBoundary.jsx
import { Component } from 'react';

class ErrorBoundary extends Component {
  state = { hasError: false, error: null };

  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  componentDidCatch(error, errorInfo) {
    console.error('React Error Boundary caught:', error, errorInfo);
    // Send to Sentry, etc.
  }

  render() {
    if (this.state.hasError) {
      return (
        <div style={{ padding: '2rem', textAlign: 'center' }}>
          <h1>Something went wrong</h1>
          <pre style={{ color: 'red' }}>{this.state.error?.message}</pre>
          <button onClick={() => window.location.reload()}>Reload</button>
        </div>
      );
    }
    return this.props.children;
  }
}

// Wrap your app:
// <ErrorBoundary><App /></ErrorBoundary>

Verify: Intentionally throw an error inside a component — you should see fallback UI, not blank.

3. Fix base URL for production

// package.json (Create React App)
{
  "homepage": "https://myapp.com"
}
// Or for subdirectory:
{
  "homepage": "/my-app"
}
// vite.config.js (Vite) [src3]
export default defineConfig({
  base: '/',  // or '/my-app/' for subdirectory
});

Verify: npx serve -s build (CRA) or npx serve -s dist (Vite) — page should render locally.

4. Fix React Router for static hosting

# Netlify: create public/_redirects
/*    /index.html   200

# Vercel: create vercel.json
{ "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] }

# Nginx:
location / {
  try_files $uri $uri/ /index.html;
}

# Apache: create public/.htaccess
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]

Verify: Navigate to a deep route, then refresh — the page should render, not 404.

5. Migrate to createRoot (React 19)

React 19 removed the legacy render API. [src7]

// ❌ React 18 and earlier (removed in React 19)
import ReactDOM from 'react-dom';
ReactDOM.render(<App />, document.getElementById('root'));

// ✅ React 19+ (required)
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(<App />);

Verify: npm run build && npx serve -s dist — app should mount without errors.

6. Configure React 19 error handlers on createRoot

React 19 adds onCaughtError, onUncaughtError, and onRecoverableError. [src7]

import { createRoot } from 'react-dom/client';

const root = createRoot(document.getElementById('root'), {
  onCaughtError: (error, errorInfo) => {
    // Sent to Error Boundary — log for analytics
    console.warn('Caught by boundary:', error.message);
  },
  onUncaughtError: (error, errorInfo) => {
    // NOT caught by any boundary — critical
    reportToSentry(error, errorInfo);
  },
  onRecoverableError: (error) => {
    // React recovered automatically
    console.info('Recovered:', error.message);
  },
});
root.render(<App />);

Code Examples

React: Complete Error Boundary with reset capability

Full script: react-complete-error-boundary-with-reset-capabilit.jsx (65 lines)

// Input:  Any React app that crashes with white screen
// Output: Graceful error UI with retry capability

import { Component } from 'react';

class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null, errorInfo: null };
  }
  // ... (see full script)

React 19: Using react-error-boundary library (functional approach)

// Input:  React 19 app that needs error boundaries without class components
// Output: Functional error boundary with automatic reset on navigation

import { ErrorBoundary } from 'react-error-boundary';  // v5.0+

function ErrorFallback({ error, resetErrorBoundary }) {
  return (
    <div role="alert" style={{ padding: '2rem' }}>
      <h2>Something went wrong</h2>
      <pre style={{ color: 'red' }}>{error.message}</pre>
      <button onClick={resetErrorBoundary}>Try again</button>
    </div>
  );
}

// Wrap your app or individual routes:
// <ErrorBoundary FallbackComponent={ErrorFallback}
//   onReset={() => window.location.reload()}
//   onError={(error, info) => logErrorToService(error, info)}>
//   <App />
// </ErrorBoundary>

Anti-Patterns

Wrong: No error handling in data rendering

// ❌ BAD — crashes with WSOD if data is null/undefined [src1]
function UserProfile({ user }) {
  return <h1>{user.name}</h1>;  // TypeError if user is undefined
}

Correct: Null checks and loading states

// ✅ GOOD — handles loading and null states [src1]
function UserProfile({ user }) {
  if (!user) return <p>Loading...</p>;
  return <h1>{user.name}</h1>;
}

Wrong: Mismatched default/named exports

// ❌ BAD — component.jsx exports named, imported as default
// component.jsx:
export function MyComponent() { return <div>Hi</div>; }
// app.jsx:
import MyComponent from './component';  // WRONG — undefined!

Correct: Match import style to export

// ✅ GOOD — consistent exports [src5]
// Option A: named export + named import
export function MyComponent() { return <div>Hi</div>; }
import { MyComponent } from './component';

// Option B: default export + default import
export default function MyComponent() { return <div>Hi</div>; }
import MyComponent from './component';

Wrong: Using removed ReactDOM.render() in React 19

// ❌ BAD — silently fails in React 19, app never mounts [src7]
import ReactDOM from 'react-dom';
ReactDOM.render(<App />, document.getElementById('root'));

Correct: Using createRoot API

// ✅ GOOD — required for React 19 [src7]
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(<App />);

Common Pitfalls

Diagnostic Commands

# Check for build errors
npm run build 2>&1 | tail -20

# Serve production build locally to reproduce
npx serve -s build   # CRA
npx serve -s dist    # Vite

# Check if index.html has correct script paths
cat build/index.html | grep -o 'src="[^"]*"'

# Verify base URL in built assets
grep -r 'base' vite.config.* 2>/dev/null
grep '"homepage"' package.json 2>/dev/null

# Check for case-sensitivity issues
find src -name "*.jsx" -o -name "*.tsx" | sort

# Check React version (is it 19+?)
npm ls react | head -5

# Check for removed React 19 APIs in codebase
grep -r "ReactDOM.render\|unmountComponentAtNode" src/ --include="*.{js,jsx,ts,tsx}"

Version History & Compatibility

React Version Status Changes Relevant to WSOD
React 16+ LTS (16.x EOL) Error Boundaries introduced [src1]
React 18 Maintained Strict Mode double-render in dev; createRoot() introduced as opt-in [src7]
React 19 (Dec 2024) Current ReactDOM.render() removed; single error log; onCaughtError / onUncaughtError callbacks [src7]

When to Use / When Not to Use

Use When Don't Use When Use Instead
App shows blank white page Page shows but layout broken CSS debugging (inspect element styles)
Console shows JavaScript errors Server returns 500 Backend/server debugging
Works in dev, blank in prod 404 on page load (HTML not served) Hosting/server configuration
React app not rendering at all React Native white screen React Native WSOD debugging

Important Caveats

Related Units