homepage/base URL, (3)
missing Error Boundary, (4) broken import/export, (5) React Router misconfiguration.ReactDOM.render() — use createRoot() or the app will
silently fail to mount. [src7]error.tsx convention — do not use class-based Error
Boundaries there. [src1]
| # | 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] |
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]
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.
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.
// 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.
# 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.
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.
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 />);
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)
// 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>
// ❌ BAD — crashes with WSOD if data is null/undefined [src1]
function UserProfile({ user }) {
return <h1>{user.name}</h1>; // TypeError if user is undefined
}
// ✅ GOOD — handles loading and null states [src1]
function UserProfile({ user }) {
if (!user) return <p>Loading...</p>;
return <h1>{user.name}</h1>;
}
// ❌ 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!
// ✅ 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';
// ❌ BAD — silently fails in React 19, app never mounts [src7]
import ReactDOM from 'react-dom';
ReactDOM.render(<App />, document.getElementById('root'));
// ✅ GOOD — required for React 19 [src7]
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(<App />);
import Header from './header' works on
macOS/Windows but fails on Linux if file is Header.jsx. Fix: match exact case. [src6]React.lazy() without
<Suspense> crashes silently. Fix: always wrap lazy components. [src1]
REACT_APP_, Vite needs
VITE_. Without prefix, they're undefined. [src2, src3]build/, Vite to dist/. Deploying
wrong folder = blank page. [src2, src3]# 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}"
| 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] |
| 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 |
error.tsx, _error.tsx) — don't use
class-based Error Boundaries in Next.js app directory.onUncaughtError and onCaughtError are set on createRoot,
not on individual components — they're app-level handlers. [src7]react-error-boundary library (v5+) provides a functional API that works with React 19
and avoids class component boilerplate. [src8]