React ホワイトスクリーン(空白ページ)の修正:コンソールエラーなしでアプリのレンダリングが失敗する

intermediate⚛️ React2026-03-23| React 16〜18、Create React App、Vite、Next.js(静的エクスポート)、各種ブラウザ、Windows/macOS/Linux

Error Message

Blank white screen with no errors in the browser (React app fails to render)
#react#ホワイトスクリーン#空白ページ#ビルド

現象の確認

Reactアプリを開いたとき — 開発モードでも本番ビルドでも — 完全に真っ白なページが表示される。エラーも出ない。スピナーも出ない。何もない。ブラウザのコンソールはきれいなままか、あるいはミニファイされたスタックトレースの奥深くに謎めいたJavaScriptエラーが1つ埋もれているだけかもしれない。

厄介なのは、Reactが明確なエラーを何も投げずに、サイレントにマウント失敗することがある点だ。ルートの <div id="root"> はただそこに空っぽのまま座っている。

まず素早く診断する

いきなりコードを変更するのはやめよう。DevToolsを開いて、次の3つを確認する:

  • Consoleタブ — 普段は無視するような警告も含め、JSエラーを探す
  • Networkタブ — JSバンドルが200ステータスで正常に読み込まれているか、それとも404になっているか
  • Elementsタブ#root を確認する。完全に空なら、Reactは一度もマウントされていない

ここで見つかった内容が、以下のどの修正が自分に当てはまるかを決める。

根本原因と修正方法

1. homepage または base パスの誤り(本番環境で最も多い原因)

https://example.com/myapp/ のようなサブディレクトリにデプロイしているのに、アプリがルート(/)に置かれているかのようにビルドしている場合、ブラウザは index.html は問題なく読み込むが、その後すべてのJSおよびCSSバンドルを取得しようとしてサイレントに404になる。Reactは実行される機会すら得られない。

Networkタブの確認: JSでフィルタリングすると、.chunk.js ファイルが200ではなく404を返しているのがわかる。

Create React Appの修正package.json に追加する:

{
  "homepage": "https://example.com/myapp"
}

Viteの修正vite.config.ts に設定する:

export default defineConfig({
  base: '/myapp/',
})

React Routerの修正BrowserRouter を使っている場合は、basename も合わせて指定する:

import { BrowserRouter } from 'react-router-dom';

<BrowserRouter basename="/myapp">
  <App />
</BrowserRouter>

再ビルドして再デプロイしよう。Networkタブですべてのアセットが200を返すようになるはずだ。

2. レンダリング中にJavaScriptエラーが発生(サイレントにキャッチされる)

React 16以降は、Error Boundaryが設置されていない場合、レンダーエラーをキャッチしてコンポーネントツリー全体をアンマウントする。開発モードでは大きな赤いオーバーレイが表示されるが、本番環境では? 真っ白なページと完全な沈黙だ。

Error Boundaryを追加すれば、何がクラッシュしているのかがついにわかるようになる:

import React from 'react';

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

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

  componentDidCatch(error, info) {
    console.error('Render error caught:', error, info);
  }

  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong: {this.state.error?.message}</h1>;
    }
    return this.props.children;
  }
}

export default ErrorBoundary;

アプリのルートを囲む:

// index.tsx または main.tsx
import ErrorBoundary from './ErrorBoundary';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <ErrorBoundary>
    <App />
  </ErrorBoundary>
);

次回レンダリング中にクラッシュが発生しても、真っ白なページの代わりにメッセージが表示されるようになる。

3. ルート要素のIDの不一致

よくあるサイレントキラーだ。index.html<div id="app"> があるのに、エントリーファイルでは document.getElementById('root') を呼んでいる。Reactは null を受け取り、マウントを完全にスキップするが、何も警告しない。

// IDが一致しない場合、getElementById はnullを返す
const container = document.getElementById('root');

// 何もしないままサイレントに失敗するのではなく、早期にエラーを投げる:
if (!container) {
  throw new Error('Root element #root not found in index.html');
}

ReactDOM.createRoot(container).render(<App />);

素早く確認する方法:Elementsタブを開いて id="root" を検索する — 大文字・小文字も含め、JS内の文字列と完全に一致していなければならない。

4. 環境変数の欠落または不正な形式

たとえばアプリがモジュールレベルで import.meta.env.VITE_API_URL を読み込んでいるとする。その変数が本番の .env ファイルに定義されていなければ、値は undefined になる。それを直ちに使おうとするコード — たとえばAxiosのベースURLの構築など — は最初のレンダリングでクラッシュする。

各ビルドターゲット用の .env ファイルが正しいか確認する:

# .env.production — CRAの場合
REACT_APP_API_URL=https://api.example.com

# .env.production — Viteの場合
VITE_API_URL=https://api.example.com

机に貼り付けておく価値のある3つのルール:

  • CRAの変数は REACT_APP_ で始まらなければならない。そうでなければビルドから見えない
  • Viteの変数は VITE_ で始まらなければならない
  • モジュールのトップレベルで環境変数を読み込まないこと — nullチェックを追加できるコンポーネントや関数の中で使う

5. 循環インポートまたはモジュールインポートの失敗

循環依存があると、モジュールが実行時に undefined として解決される。そのモジュールから関数を呼び出そうとするコードは最初のレンダリングでクラッシュする — 本番環境ではサイレントに。

まず、ビルド自体がエラーを報告していないか確認する:

# CRA
npm run build 2>&1 | grep -i error

# Vite
npx vite build 2>&1 | grep -i error

循環インポートを直接見つけるには、madge が最良の友だ:

npx madge --circular --extensions ts,tsx src/

共有の型やユーティリティをインポートループの外側に置く別ファイルに移動することで循環を断ち切る — サイクル内のものはそこからインポートしない。

6. Reactのバージョン競合

1つのバンドルに2つのバージョンのReactが存在すると、互いに知らない2つの別々のフックシステムができてしまう。フックが壊れ、アプリはレンダリングされない。これは通常、サードパーティライブラリがReactをピア依存として扱わず、独自のReactのコピーをバンドルしているときに起きる。

npm ls react
# または
pnpm why react

2つの異なるバージョンを見つけたら? package.json で両方を固定する:

"resolutions": {
  "react": "^18.2.0",
  "react-dom": "^18.2.0"
}

npm install --force を実行し、再ビルドして、再度 npm ls react を確認する — ツリー全体で単一バージョンが表示されるはずだ。

予防策

  • アプリのルートにError Boundaryを追加する — 本番環境では必須だ。サイレントな失敗は大きな失敗であるべきだ。
  • リリース前にローカルで本番ビルドをテストするnpm run build && npx serve -s build(CRA)または npx vite preview(Vite)
  • 最初から base/homepage を設定する — アプリがサブディレクトリに置かれることがわかっているなら、後から修正すると必ず予期しない問題が起きる
  • 起動時に必須の環境変数を検証する — 10秒後に真っ白な画面ではなく、即座に明確なエラーを投げるようにする
  • ステージング環境でソースマップを有効にする — ソースマップなしでミニファイされた本番エラーをデバッグするのは、通りの名前がない地図を読むようなものだ

確認チェックリスト

  • [ ] Networkタブ:すべてのJS/CSSバンドルが200を返し、404になっているものがないこと
  • [ ] Elementsタブ:ページ読み込み後、#root に子要素があること
  • [ ] Console:未キャッチのエラーや未処理のPromise拒否がないこと
  • [ ] Error Boundary:クラッシュ時に真っ白になる代わりにフォールバックUIが表示されること
  • [ ] ローカルの本番プレビューが通過すること:デプロイ前に npx serve -s build が動作すること

Related Error Notes