🔄

React SPAをNginxでホスティングする際のルーティング404対策(try_files設定)

に公開

背景

発生した問題

ReactでSPA(Single Page Application)を開発し、ECS上のNginxでホスティングしたところ、ローカル環境と挙動が異なる問題が発生しました。

症状:

環境 アクセス方法 結果
ローカル(npm start) http://localhost:3000/loginに直接アクセス ✅ ログイン画面が表示される
ローカル(npm start) /loginページでリロード(⌘+R) ✅ ログイン画面が表示される
Nginx(本番) https://app.example.com/loginに直接アクセス 404 NOT FOUND
Nginx(本番) /loginページでリロード 404 NOT FOUND

問題の本質

この問題は、SPAのクライアントサイドルーティングとNginxのサーバーサイドルーティングの仕組みの違いから発生します。

SPAのルーティングの仕組み

クライアントサイドルーティング

React RouterなどのSPAでは、ルーティングはブラウザ上で行われます(クライアントサイドルーティング)。

SPAの動作フロー

1. 初回アクセス: https://app.example.com/
   ↓
2. Nginxが index.html を返す
   ↓
3. ブラウザが index.html を読み込み、React アプリが起動
   ↓
4. React Router が /login に遷移(履歴APIを使用)
   ↓
5. ブラウザのURLバーに /login と表示される
   (実際にはサーバーにリクエストは送信されない)

リロード時の問題

1. ブラウザでリロード(⌘+R)
   ↓
2. ブラウザが https://app.example.com/login にGETリクエスト
   ↓
3. Nginxが /login というファイルを探す
   ↓
4. ファイルが存在しないため 404 NOT FOUND を返す

webpack-dev-serverとNginxの違い

項目 webpack-dev-server(ローカル) Nginx(本番)
用途 開発専用サーバー 本番用Webサーバー
historyApiFallback ✅ デフォルトで有効 ❌ 明示的に設定が必要
存在しないパスへのアクセス index.htmlを返す 404エラーを返す
動作 すべてのリクエストをindex.htmlにリダイレクト リクエストされたパスに対応するファイルを探す

historyApiFallbackとは?

概要

historyApiFallbackは、SPAのクライアントサイドルーティングを正しく機能させるための仕組みです。

目的

存在しないパスへのリクエストがあった場合に、404エラーを返さず、常にindex.htmlを返すことで、React Routerなどのクライアントサイドルーティングライブラリが正しく動作するようにします。

動作イメージ

historyApiFallback無効(問題あり)

ブラウザ: /login にアクセス
    ↓
Nginx: /login というファイルを探す
    ↓
Nginx: ファイルが見つからない
    ↓
Nginx: 404 NOT FOUND を返す
    ↓
ブラウザ: エラーページを表示

historyApiFallback有効(正常)

ブラウザ: /login にアクセス
    ↓
Nginx: /login というファイルを探す
    ↓
Nginx: ファイルが見つからない
    ↓
Nginx: index.html を返す(fallback)
    ↓
ブラウザ: index.html を読み込み、Reactアプリ起動
    ↓
React Router: URLの /login を解析してログイン画面を表示

利用シーン

以下のような場面で必要になります:

  1. 直接URLアクセス: https://app.example.com/loginに直接アクセス
  2. ページリロード: SPAの任意のページでブラウザのリロードボタンを押す
  3. ブックマーク: SPAの特定ページをブックマークして後日アクセス
  4. リンク共有: SNSなどでSPAの特定ページのURLを共有

Nginxでの実装

基本的な設定(try_files)

Nginxでtry_filesディレクティブを使用して、historyApiFallbackを実装します。

default.conf

server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;

    # gzip圧縮を有効化(推奨)
    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
    gzip_comp_level 6;
    gzip_min_length 1000;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        try_files $uri $uri/ /index.html;
    }

    # エラーページのカスタマイズ(任意)
    error_page  404              /index.html;
    error_page  500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

try_filesの動作詳細

try_files $uri $uri/ /index.html;の動作を順を追って説明します。

リクエスト: /loginの場合

ステップ 試行 説明 結果
1 $uri /usr/share/nginx/html/loginというファイルを探す ❌ 見つからない
2 $uri/ /usr/share/nginx/html/login/というディレクトリを探す ❌ 見つからない
3 /index.html /usr/share/nginx/html/index.htmlを返す ✅ 成功

リクエスト: /static/js/main.jsの場合

ステップ 試行 説明 結果
1 $uri /usr/share/nginx/html/static/js/main.jsというファイルを探す ✅ 見つかった

ポイント: 実際のファイル(JS, CSS, 画像など)は正しく配信され、存在しないパスのみindex.htmlにフォールバックされます。

まとめ

項目 内容
問題 SPAのクライアントサイドルーティングでリロード時に404エラー
原因 Nginxがリクエストされたパスに対応するファイルを探すため
解決策 try_files $uri $uri/ /index.html;で存在しないパスをindex.htmlにフォールバック
重要ポイント 実際のファイル(JS, CSS)は正しく配信され、存在しないパスのみフォールバックされる

実装のステップ

  1. Nginx設定ファイルを作成: try_files $uri $uri/ /index.html;を含める
  2. Dockerfileでコピー: 設定ファイルを適切な場所に配置
  3. ビルドとテスト: ローカルで動作確認
  4. デプロイ: ECSなどにデプロイ
  5. 動作確認: 直接アクセスとリロードをテスト

注意事項

  • APIプロキシはlocation /より上に配置する
  • index.htmlはキャッシュしない設定にする
  • セキュリティヘッダーとgzip圧縮を有効化する
  • ヘルスチェックエンドポイントを実装する

この設定により、React SPAをNginxで正しくホスティングでき、ユーザーが任意のURLに直接アクセスしたりリロードしたりしても、正常に動作するようになります。

GitHubで編集を提案

Discussion