---
# === IDENTITY ===
id: software/debugging/react-white-screen/2026
canonical_question: "How do I fix the white screen of death in React?"
aliases:
  - "React white screen of death"
  - "React blank page"
  - "React app shows nothing"
  - "React white screen production"
  - "React build blank page"
  - "React app not rendering"
  - "React WSOD"
  - "React 19 white screen"
  - "Vite React blank page deploy"
entity_type: software_reference
domain: software > debugging > react_white_screen
region: global
jurisdiction: global
temporal_scope: 2020-2026

# === VERIFICATION ===
last_verified: 2026-05-17
confidence: 0.93
version: 2.1
first_published: 2026-02-17

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "React 19.2 (2025-10) — React Compiler enabled by default in modern bundlers; error handling APIs unchanged from 19.0"
  next_review: 2026-11-13
  change_sensitivity: medium

# === CONSTRAINTS ===
constraints:
  - "Error Boundaries only catch errors in rendering, lifecycle methods, and constructors — NOT event handlers, async code, or SSR"
  - "React 19 removed ReactDOM.render() — use createRoot() or the app will not mount at all"
  - "Never suppress console errors to 'fix' WSOD — the console error IS the diagnostic"
  - "Next.js app directory uses its own error.tsx convention — do not use class-based Error Boundaries there"
  - "Production builds minify React error messages — deploy source maps or use Sentry for readable traces"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Page renders but layout/styling is broken (not blank)"
    use_instead: "CSS debugging — inspect element styles, not JS errors"
  - condition: "Server returns 500 error before React loads"
    use_instead: "Backend/server debugging — check server logs, not browser console"
  - condition: "React Native app shows white screen"
    use_instead: "React Native WSOD debugging — different root causes (native modules, Metro bundler)"

# === AGENT HINTS ===
inputs_needed:
  - key: build_tool
    question: "Which build tool does the project use?"
    type: choice
    options: ["Create React App", "Vite", "Next.js", "Webpack (custom)", "Other"]
  - key: environment
    question: "Where does the white screen appear?"
    type: choice
    options: ["Only in production", "Only in development", "Both dev and prod"]
  - key: react_version
    question: "Which React version is the project using?"
    type: choice
    options: ["React 16-17", "React 18", "React 19+"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/debugging/react-white-screen/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-05-17)"

# === RELATED UNITS ===
related_kos:
  related_to:
    - id: "software/debugging/react-too-many-rerenders/2026"
      label: "React Too Many Re-renders"
    - id: "software/debugging/spring-boot-npe/2026"
      label: "Spring Boot NullPointerException"
  often_confused_with:
    - id: "software/debugging/react-hydration-mismatch/2026"
      label: "React Hydration Mismatch Errors"

# === SOURCES (9 authoritative sources) ===
sources:
  - id: src1
    title: "React Error Boundaries"
    author: React.dev
    url: https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary
    type: official_docs
    published: 2025-09-01
    reliability: authoritative
  - id: src2
    title: "Create React App — Deployment"
    author: Create React App
    url: https://create-react-app.dev/docs/deployment/
    type: official_docs
    published: 2025-03-01
    reliability: authoritative
  - id: src3
    title: "Vite Configuration — Base URL"
    author: Vite
    url: https://vitejs.dev/config/shared-options.html#base
    type: official_docs
    published: 2025-10-01
    reliability: authoritative
  - id: src4
    title: "React Router Documentation"
    author: React Router
    url: https://reactrouter.com/en/main
    type: official_docs
    published: 2025-08-01
    reliability: authoritative
  - id: src5
    title: "Chrome DevTools — Console and Network Tabs"
    author: Google
    url: https://developer.chrome.com/docs/devtools/
    type: official_docs
    published: 2025-10-01
    reliability: authoritative
  - id: src6
    title: "Webpack Troubleshooting — Blank Page After Build"
    author: Webpack
    url: https://webpack.js.org/guides/
    type: official_docs
    published: 2025-06-01
    reliability: authoritative
  - id: src7
    title: "React v19 Release — Error Handling Improvements"
    author: React.dev
    url: https://react.dev/blog/2024/12/05/react-19
    type: official_docs
    published: 2024-12-05
    reliability: authoritative
  - id: src8
    title: "react-error-boundary — Modern Error Boundary Library"
    author: Brian Vaughn
    url: https://www.npmjs.com/package/react-error-boundary
    type: community_resource
    published: 2025-06-01
    reliability: high
  - id: src9
    title: "React 19.2 Release Notes"
    author: React.dev
    url: https://react.dev/blog/2025/10/01/react-19-2
    type: official_docs
    published: 2025-10-01
    reliability: authoritative
---

# How to Fix the White Screen of Death in React

## TL;DR

- **Bottom line**: React's white screen (WSOD) is almost always a JavaScript error that crashes before render. The 5 most common causes: (1) unhandled exception in render, (2) wrong `homepage`/`base` URL in production, (3) missing error boundary, (4) broken import/export, (5) React Router misconfiguration.
- **Key tool/command**: Open browser DevTools Console (F12) — the error that caused the blank screen is always logged there.
- **Watch out for**: Production builds hide error details. An app that works in dev (`npm start`) but shows blank in production is almost always a base URL / routing issue.
- **Works with**: React 16-19+, Create React App, Vite, Next.js, any React bundler.

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- Error Boundaries only catch errors in rendering, lifecycle methods, and constructors — NOT event handlers, async code, or SSR. For those, use try/catch.
- React 19 removed `ReactDOM.render()` — use `createRoot()` or the app will silently fail to mount. [src7]
- Never suppress console errors to "fix" WSOD — the console error IS the diagnostic. Always read it first. [src5]
- Next.js app directory uses its own `error.tsx` convention — do not use class-based Error Boundaries there. [src1]
- Production builds minify React error messages — deploy source maps or use Sentry for readable stack traces.

## Quick Reference

| # | Cause | Likelihood | Signature | Fix |
|---|---|---|---|---|
| 1 | Unhandled render error | ~30% of cases | Works in dev, blank in prod | Add Error Boundary [src1] |
| 2 | Wrong `homepage`/`base` URL | ~25% of cases | Assets return 404 in prod | Set correct `homepage` in package.json or `base` in vite.config [src2, src3] |
| 3 | Broken import/export | ~15% of cases | "X is not a component" error | Fix default/named export mismatch [src5] |
| 4 | React Router on static host | ~10% of cases | Root works, routes return 404 on refresh | Add `_redirects`, `vercel.json`, or `nginx try_files` [src4] |
| 5 | Async data crash | ~8% of cases | Renders then goes blank | Add null checks / optional chaining before data loads [src1] |
| 6 | Missing return in component | ~5% of cases | No error, just blank | Ensure JSX is returned (not just side effects) [src5] |
| 7 | CORS/network error | ~4% of cases | Blank + console errors | Fix API CORS or add proxy config [src5] |
| 8 | Minification/case error | ~3% of cases | Works in dev, crashes in prod build | Check case-sensitivity in imports (Linux is case-sensitive) [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 yet [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 / lazy load 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 in console? → Check Network tab (F12 > Network)
│       ├── JS/CSS files 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 in HTML
├── Works in dev but not prod?
│   ├── Routes work at root but 404 on refresh → Routing issue [src4]
│   │   └── FIX: Configure server for SPA fallback
│   ├── Blank on all routes → base URL or build issue
│   └── React 19 migration? → Check for removed APIs (ReactDOM.render, unmountComponentAtNode) [src7]
└── DEFAULT → Add Error Boundary to catch and display errors [src1]
```

## Decision Logic

### If the browser console shows a JavaScript error
--> Read the error message verbatim — that error IS the diagnostic. Open DevTools (F12) > Console, fix the specific error before doing anything else. [src5]

### If the app works in dev (`npm start`) but blank in production
--> Check `homepage` (CRA) or `base` (Vite) in your config first — wrong base URL accounts for ~25% of WSOD cases. [src2, src3]

### If a route works at `/` but returns 404 on refresh
--> Configure SPA fallback on your static host (`_redirects` for Netlify, `vercel.json` for Vercel, `try_files` for nginx). React Router cannot recover from a 404 server response. [src4]

### If you migrated to React 19+ and the app silently fails to mount
--> Replace `ReactDOM.render()` with `createRoot()` from `react-dom/client`. The legacy API was removed in 19.0 and still ships in many tutorials. [src7]

### If lazy-loaded chunks intermittently fail after a deploy
--> Wrap `React.lazy()` calls in a retry wrapper with cache busting and ensure every lazy component is inside `<Suspense fallback={...}>`. Stale CDN chunks are a top-3 2026 cause. [src1, src9]

### If you need error reporting that survives page unload
--> Use `navigator.sendBeacon('/api/errors', payload)` in `componentDidCatch` — `fetch()` is killed when the user closes the tab, `sendBeacon` is not.

### If you've added Error Boundaries and still see WSOD on event-handler errors
--> Error Boundaries do NOT catch event-handler, async, or SSR errors. Wrap async handlers in try/catch and call `setError(err)` to push the error into the boundary, or use the `react-error-boundary` library's `useErrorBoundary` hook. [src1, src8]

### DEFAULT
--> Add a top-level Error Boundary, set `onUncaughtError` on `createRoot`, and ship source maps. WSOD without diagnostics is a 30-minute fix that becomes a 30-hour fix without these three. [src1, src7]

## Step-by-Step Guide

### 1. Check the browser console (F12)

Always start here. The error that killed the render is logged in the console. [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 a fallback UI instead of a blank screen. [src1]

```jsx
// 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 error tracking (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 the fallback UI, not a blank page.

### 3. Fix base URL for production deployment

```json
// package.json (Create React App)
{
  "homepage": "https://myapp.com"
}
// Or for subdirectory:
{
  "homepage": "/my-app"
}
```

```javascript
// vite.config.js (Vite)
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 browser — the page should render, not 404.

### 5. Migrate to createRoot (React 19)

If upgrading to React 19, the old render API is removed. [src7]

```jsx
// ❌ 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` callbacks. [src7]

```jsx
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](scripts/react-complete-error-boundary-with-reset-capabilit.jsx) (65 lines)

```jsx
// Input:  Any React app that crashes with white screen
// Output: Graceful error UI with retry capability
import { Component, useState, useCallback } from 'react';
class ErrorBoundary extends Component {
  constructor(props) {
# ... (see full script)
```

### React 19: Using react-error-boundary library (functional approach)

```jsx
// 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>
```

### React 19.2: Lazy-load retry wrapper for stale chunks after deploy

```jsx
// Input:  React.lazy() that intermittently fails with ChunkLoadError after deploys
// Output: Auto-retry + cache clear + reload on persistent failure
import { lazy } from 'react';

function lazyWithRetry(importFn, retries = 2) {
  return lazy(async () => {
    try {
      return await importFn();
    } catch (err) {
      if (retries > 0 && /ChunkLoadError|Loading chunk/.test(err.message)) {
        // Clear stale chunks and retry once
        if ('caches' in window) {
          const keys = await caches.keys();
          await Promise.all(keys.map((k) => caches.delete(k)));
        }
        return lazyWithRetry(importFn, retries - 1)._payload._result;
      }
      // Final fallback: hard reload to fetch latest index.html
      window.location.reload();
      throw err;
    }
  });
}

const Dashboard = lazyWithRetry(() => import('./Dashboard'));
// <Suspense fallback={<Loading />}><Dashboard /></Suspense>
```

## Anti-Patterns

### Wrong: No error handling in data rendering

```jsx
// ❌ 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

```jsx
// ✅ 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

```jsx
// ❌ BAD — component.jsx exports named, but 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

```jsx
// ✅ 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

```jsx
// ❌ 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

```jsx
// ✅ GOOD — required for React 19 [src7]
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(<App />);
```

## Common Pitfalls

- **Case-sensitive imports on Linux**: `import Header from './header'` works on macOS/Windows but fails on Linux if file is `Header.jsx`. Fix: match exact case. [src6]
- **Missing Suspense for lazy components**: `React.lazy()` without `<Suspense>` wrapper crashes silently. Fix: always wrap lazy components in `<Suspense fallback={<Loading/>}>`. [src1]
- **Environment variables not prefixed**: In CRA, env vars need `REACT_APP_` prefix. In Vite, `VITE_` prefix. Without prefix, they're undefined and can crash. [src2, src3]
- **StrictMode double render**: React StrictMode in dev renders twice, which can expose race conditions that cause WSOD. Fix: ensure components handle double-render gracefully. [src1]
- **Build path mismatch**: CRA puts build in `build/`, Vite in `dist/`. Deploying wrong folder results in blank page. Fix: check your deploy config points to the correct output directory. [src2, src3]
- **React 19 error log deduplication**: React 19 logs errors once instead of three times (React 18 behavior). If your monitoring relies on counting console.error calls, update your tooling. [src7]
- **React Router v7 fog of war**: React Router v7 introduced deferred route manifests ("fog of war") that can cause blank pages if the manifest fails to load. Fix: ensure your server correctly serves the route manifest file. [src4]
- **Stale lazy chunks after deploy**: After a new deploy, browsers with the old `index.html` cached try to fetch chunk hashes that no longer exist. Fix: wrap `React.lazy()` in a retry-with-reload wrapper (catch `ChunkLoadError`, clear `caches`, `location.reload(true)`).
- **React 19.2 Compiler skips violations silently**: As of React 19.2 (October 2025), the Compiler is enabled by default in modern Vite/CRA setups and automatically skips components with violations rather than failing the build. A component with rules-of-hooks violations may render in dev (un-optimized) but crash in production once a different code path runs. Fix: enable `eslint-plugin-react-hooks` with the new compiler-powered rules and treat warnings as errors. [src9]
- **Error reporting lost on tab close**: A `fetch('/api/errors', ...)` call in `componentDidCatch` is killed if the user closes the tab before it completes. Fix: use `navigator.sendBeacon('/api/errors', JSON.stringify(payload))` — it is guaranteed to fire even on unload.

## Diagnostic Commands

```bash
# 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) | LTS | `ReactDOM.render()` removed; single error log instead of triple; `onCaughtError` / `onUncaughtError` callbacks on `createRoot` [src7] |
| React 19.2 (Oct 2025) | Current | React Compiler enabled by default in Vite/CRA; Compiler skips violation components silently (now show in dev but crash in prod if untested); error-handling APIs unchanged from 19.0; improved component stacks for debugging [src9] |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| App shows blank white page | Page shows but layout is 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 (different root causes) |

## Important Caveats

- Error Boundaries only catch errors in rendering, lifecycle methods, and constructors — NOT in event handlers, async code, or SSR. For those, use try/catch.
- In production builds, React error messages are minified. Use source maps or Sentry for readable stack traces.
- Next.js has its own error handling (`error.tsx`, `_error.tsx`) — don't use class-based Error Boundaries in Next.js app directory.
- React 19's `onUncaughtError` and `onCaughtError` are set on `createRoot`, not on individual components — they're app-level handlers. [src7]
- The `react-error-boundary` library (v5+) provides a functional API that works with React 19 and avoids class component boilerplate. [src8]

## Related Units

- [React Too Many Re-renders](/software/debugging/react-too-many-rerenders/2026)
- [Spring Boot NullPointerException](/software/debugging/spring-boot-npe/2026)
