Webサイトやアプリをデプロイ後に出る404エラーの直し方
アプリはローカルでは動くのに、デプロイ後にページが404を返す場合、最も多い原因はシングルページアプリ(SPA)のルーティングが設定されていないことです。サーバーは/dashboardというファイルを探しますが、そのルートはJavaScriptの中にしか存在しないため、そのようなファイルはありません。解決策は、アプリのルートに対してindex.htmlを返すフォールバックのルールを設定することです。そのほかの原因として、ビルド出力にファイルがない、パスの大文字と小文字の違い、末尾のスラッシュの違い、誤ったベースパス、そして一度もデプロイされていないAPIルートなどがあります。
まず、どのURLが404になっているのか、そしてどうやってそこにたどり着いたのかを正確に確認しましょう。症状のパターンから原因がすぐに分かることが多いです。
ステップ1:パターンを見つける
| 見えている症状 | もっとも可能性の高い原因 | 参照先 |
|---|---|---|
| トップページは表示され、リンクもクリックで移動できるが、再読み込みや直接開いた深いリンクで404になる | SPAのフォールバックがない | ステップ2 |
| トップページを含め、すべてのページが404になる | 間違ったフォルダを公開している、またはビルドがデプロイされていない | ステップ3 |
| ページは読み込まれるが、画像、フォント、スクリプトが404になる | ビルドにファイルがない、またはパスが誤っている | ステップ4 |
/about/は動くが/aboutは失敗する(またはその逆) | 末尾のスラッシュの扱い | ステップ5 |
/Aboutは失敗するが/aboutは動く | サーバーが大文字小文字を区別する | ステップ4 |
ページは読み込まれるが、/api/...へのデータ取得が404になる | APIがデプロイされていない、またはルーティングされていない | ステップ6 |
| 以前のサイトの古いURLが404になる | リダイレクトがない | ステップ7 |
| カスタムドメインでのみ起きる | DNSまたはドメインの設定 | ステップ8 |
実際のステータスコードを確認するには、開発者ツールを開き(右クリック → 検証)、ネットワークタブに移動して再読み込みし、最初のリクエストのステータスを見ましょう。
ステップ2:SPAのフォールバックを追加する
これは典型的な原因です。React、Vue、Svelteのシングルページアプリでは、サーバーが持つ実際のファイルはわずかです。index.htmlと、いくつかのJavaScriptとCSSだけです。/dashboardや/projects/42のようなルートは、ブラウザ内のルーターが処理します。
- リンクをクリックすると動きます。ルーターがURLを変え、サーバーに尋ねることなく新しいページを描画するからです。
- 再読み込みしたり、リンクを直接開いたりすると失敗します。ブラウザが
/dashboardをサーバーに要求し、サーバーにはそのファイルがないため、404を返します。
解決策は、「リクエストが実際のファイルに一致しない場合は、ステータス200でindex.htmlを返し、ルーターに処理を任せる」というルールです。追加方法はホストによって異なります(詳細は変わることがあるため、ホストの最新のドキュメントを確認してください)。
| ホスト | 一般的なフォールバックのルール |
|---|---|
| Netlify | 公開フォルダ内の_redirectsファイルに/* /index.html 200と書く |
| Vercel | vercel.jsonですべてのパスを/index.htmlにリライトする(フレームワークのプリセットで自動的に処理されることが多い) |
| Cloudflare Pages | トップレベルに404.htmlがない場合、デフォルトでSPA形式のフォールバックを提供する |
| Nginx | location /ブロックにtry_files $uri $uri/ /index.html;を書く |
| Apache | ファイルでないリクエストをindex.htmlに送る、.htaccessのリライトルール |
| GitHub Pages | サーバー側のリライトはない。よくある回避策は、ハッシュルーティングか、リダイレクトする404.htmlを用意すること |
このルールが実際のファイルを飲み込まないよう注意してください。/assets/app.jsへのリクエストは、JavaScriptファイルを返し続ける必要があります。もし代わりにindex.htmlが返ると、「Failed to load module script」というエラーとともに白い空のページになります。真っ白なページの直し方をご覧ください。
また、どのページにも一致しないURLで何も表示されないのではなく、親切な「ページが見つかりません」が表示されるように、ルーターにキャッチオールのルートを用意しましょう。
サーバーの設定を変更できない場合は、ハッシュルーティング(/#/dashboardのようなURL)を使うと問題を避けられます。#以降の部分はサーバーに送られないからです。これは最初の選択肢ではなく回避策です。URLは見栄えが悪く、検索エンジンにも親切ではありません。
サーバーでページを描画したり、ビルド時にページを生成したりするフレームワーク(Next.js、Astroなど)では、各ページに対応する実際のファイルやサーバーのルートがあるのが普通なので、この問題は比較的起きにくいです。それらでの同等の問題は、ビルド時に生成されなかったページです。
ステップ3:実際にデプロイされたものを確認する
すべてが404になる場合、サーバーがビルドされたアプリを配信できていない可能性が高いです。
- 正しいフォルダ。 ホストは、プロジェクトのルートではなく、ビルドの出力(Viteなら通常
dist、Create React Appならbuild、静的なNext.jsのエクスポートならout)を公開する必要があります。 - ビルドが実行された。 デプロイのログを読みましょう。ビルドが失敗していれば、ホストが何もデプロイしなかったか、古いバージョンをデプロイした可能性があります。
index.htmlが公開フォルダの最上位にある。 サブフォルダの中ではいけません。- ベースパスが一致している。 アプリが
example.com/app/で配信されるなら、そのベース(Viteのbase、React Routerのbasename)でビルドされている必要があります。そうでないと、すべてのルートとアセットが見つからなくなります。
ステップ4:ファイルの不足とパスの誤りを直す
ページは読み込まれるのにアセットが404になる場合は、次を確認しましょう。
- 大文字と小文字の区別。 macOSやWindowsでは、通常
Logo.pngとlogo.pngを同じファイルとして扱います。しかしほとんどのLinuxサーバーは区別します。すべての参照を、実際のファイル名と完全に一致させましょう。 - ビルドの外にあるファイル。 Viteでは、
public/にあるファイルはそのままコピーされ、ルートから参照されます(/logo.png)。コード内でインポートされたファイルはバンドルされます。プロジェクト内の別の場所にあるファイルは、まったくデプロイされない可能性があります。 - 相対パス。
images/photo.jpgは現在のURLを基準に解決されるため、/blog/post-1では壊れます。/images/photo.jpgのように書きましょう。 - ローカルディスクに保存されたアップロードファイル。 利用者のアップロードをサーバー自身のフォルダに書き込んでいた場合、新しいバージョンをデプロイすると消えてしまいます。適切なファイルストレージを使いましょう。
ステップ5:末尾のスラッシュを統一する
/aboutと/about/は、別のURLです。ホストによって、スラッシュを付けるか削除するかが異なります。リンクやルーターとの不一致は、404やリダイレクトのループを引き起こします。どちらかの形式に統一し、リンクもそれに合わせ、もう一方の形式を統一した形式にリダイレクトするようホストを設定しましょう。統一は検索エンジンにとっても役立ちます。統一しないと、同じページが2つあると認識されることがあるからです。
ステップ6:404になるAPIルートを直す
ページは読み込まれるのに、ネットワークタブで/api/...のリクエストが404を返している場合は、次を確認しましょう。
- バックエンドがデプロイされていない。 静的ホストはファイルを配信するだけです。バックエンドはどこかで動いている必要があります。
- 本番環境ではパスのルーティングが異なる。 開発環境では、プロキシが
/apiを別のサーバーに転送していたかもしれません。本番環境では、ホストが同等のリライトを必要とするか、フロントエンドがAPIの実際のアドレスを呼び出す必要があります。 - SPAのフォールバックが横取りしている。 APIへのリクエストが、ステータス200で
index.htmlを返している場合、フォールバックのルールが広すぎます。/api/*をそこから除外しましょう。
公開されたアプリから/api/ordersへのリクエストが404を返しますが、プレビューでは動作します。プレビューと公開アプリで/apiがどうルーティングされているかを確認し、公開されたフロントエンドがバックエンドに同じ方法で届くようにしてください。
ステップ7:古いURLをリダイレクトする
別のサイトから移行したり、構成を作り直したりした場合、利用者や検索エンジンがまだ使っている古いURLは404になります。古いURLそれぞれから、新しい場所への301(恒久的な)リダイレクトを追加しましょう。一覧を作り、公開後にサンプルをテストしてください。一覧の作り方については、WordPressサイトの移行方法で説明しています。
ステップ8:ドメインを確認する
ホストのアドレスではアプリが動くのに、カスタムドメインで404になる場合、ドメインが間違ったプロジェクトを指しているか、ホスト側でまだそのサイトにドメインが紐づけられていない可能性があります。DNSレコードとホストのドメイン設定を確認してください。カスタムドメインの接続方法をご覧ください。
本物の404ページを役立つものにする
404の中には正しいものもあります。URLを打ち間違えた場合などです。そのページを役立つものにしましょう。
- 短いメッセージと、トップページや主要なセクションへのリンク
- サイトに検索機能があれば、検索ボックス
- サイトの他のページと同じヘッダーとナビゲーション
設定が許すなら、存在しないページには本物の404ステータスを返しましょう。「見つからない」というメッセージをステータス200で返すのは、検索エンジンがソフト404と呼ぶものです。クロールの時間を無駄にし、インデックス登録を混乱させることがあります。
よくある間違い
- クリックだけでテストする。 必ず、深いURLを新しいタブに貼り付けて再読み込みしてテストしましょう。
- すべてを捕まえてしまうフォールバック。 アセットやAPIのルートが
index.htmlを返すと、白いページや分かりにくいAPIエラーの原因になります。 - サーバー上でだけ壊れる、大文字と小文字の不一致。
- ビルド出力ではなく、ソースのフォルダをデプロイする。
- リダイレクトなしでURLを再構成する。 古いリンクすべてが404になります。
Mythexでの404
Mythexでは、ワークスペースから公開し、静的なフロントエンドは静的サイトとしてデプロイされます。公開されたページが404になる場合は、エージェントに正確なURL、再読み込み時かクリック時か、ネットワークタブに何が表示されるかを伝えたうえで、ルーティングを確認するよう頼みましょう。修正後は再度公開してください。公開中のアプリは、再公開するまでスナップショットだからです。最近プロジェクトを変更したのに公開サイトが古く見える場合は、再度公開して強制再読み込みしてください。ビルドが失敗したら、公開ダイアログのAIで修正を使いましょう。ドキュメントの公開方法と公開のトラブルシューティング、そしてAIで作ったアプリのデバッグ方法のガイドもご覧ください。
404のチェックリスト
- どのURLが404になるか、クリック時か、再読み込み時か、両方かを正確に把握した
- SPAのフォールバックが、アプリのルートに対してステータス200で
index.htmlを返している - フォールバックが実際のファイルと
/api/*を除外している - ルーターに、キャッチオールの「見つかりません」ルートがある
- 公開されているのはビルド出力のフォルダで、ビルドは成功している
- ベースパスが、アプリの配信先と一致している
- すべての参照でファイル名の大文字と小文字が一致している
- アセットのパスが
/から始まっている - 末尾のスラッシュの形式が統一されている
- APIルートが本番でデプロイされ、ルーティングされている
- 古いURLに301リダイレクトを設定した
- 深いリンクを新しいタブに貼り付けてテストした
よくある質問
Reactアプリでページを再読み込みすると404になるのはなぜですか?
シングルページアプリでは、/dashboardのようなルートはJavaScriptの中にしか存在しないからです。リンクをクリックして移動できるのは、ルーターがブラウザ内でそれを処理しているためです。しかし再読み込みすると、ブラウザはサーバーに存在しない/dashboardというファイルを要求することになります。解決策は、アプリのルートへのリクエストにindex.htmlを返すフォールバックのルールを設定することです。
Netlify、Vercel、NginxでSPAのフォールバックを設定するにはどうすればよいですか?
Netlifyでは、/* /index.html 200 と書いた_redirectsファイルを追加します。Vercelでは、すべてのパスを/index.htmlに送るリライトをvercel.jsonに追加します。Nginxでは、locationブロックにtry_files $uri $uri/ /index.html; を書きます。ルールは変わることがあるため、ホストのドキュメントも確認してください。
デプロイ後に画像やファイルが404になるのはなぜですか?
通常は、ファイルがビルドの出力に含まれていないか、パスの大文字と小文字が一致していないか(サーバーはノートPCと違って大文字小文字を区別することが多いです)、スラッシュから始めるべきパスが相対パスになっているかのいずれかです。
存在しないページは、本物の404ステータスを返すべきですか?
はい。役立つ「ページが見つかりません」の画面を表示しつつ、環境が許す場合は404ステータスを返しましょう。検索エンジンは、「見つからない」というメッセージをステータス200で返すページを、ソフト404として扱います。これはクロールの時間を無駄にし、インデックス登録を混乱させることがあります。