🔄
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 を解析してログイン画面を表示
利用シーン
以下のような場面で必要になります:
-
直接URLアクセス:
https://app.example.com/loginに直接アクセス - ページリロード: SPAの任意のページでブラウザのリロードボタンを押す
- ブックマーク: SPAの特定ページをブックマークして後日アクセス
- リンク共有: 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)は正しく配信され、存在しないパスのみフォールバックされる |
実装のステップ
-
Nginx設定ファイルを作成:
try_files $uri $uri/ /index.html;を含める - Dockerfileでコピー: 設定ファイルを適切な場所に配置
- ビルドとテスト: ローカルで動作確認
- デプロイ: ECSなどにデプロイ
- 動作確認: 直接アクセスとリロードをテスト
注意事項
- APIプロキシは
location /より上に配置する -
index.htmlはキャッシュしない設定にする - セキュリティヘッダーとgzip圧縮を有効化する
- ヘルスチェックエンドポイントを実装する
この設定により、React SPAをNginxで正しくホスティングでき、ユーザーが任意のURLに直接アクセスしたりリロードしたりしても、正常に動作するようになります。
Discussion