SPAの404問題の本質 ― ルーティングとサーバ設定の関係

SPAは壊れているのではなく「サーバが正しく動いている」

SPA(Single Page Application)を公開した直後に、多くの人が遭遇する現象があります。

トップページは表示できるのに、URLを直接開くと404になる。

リンククリックでは遷移できるのに、リロードすると「ページが見つかりません」になる。React、Vue、Next.js、Nuxt、どれを使っても起きます。これはフレームワークの不具合ではありません。サーバが正しく404を返しているから発生します

つまりこの問題はフロントエンドではなく、HTTPとWebサーバの仕組みの問題です。

そもそもSPAのルーティングとは何か

通常のWebサイトでは、URLはそのままファイルの場所を意味します。

URL サーバの解釈
/about.html about.htmlを返す
/contact.html contact.htmlを返す

しかしSPAでは違います。URLは「画面の状態」を表しているだけで、実際のファイルは存在しません。

例:

  • /users/1
  • /settings/profile
  • /dashboard

これらのHTMLファイルはサーバ上に存在していません。存在するのは1つだけです。

  • index.html

SPAはこのindex.htmlを読み込んだ後、JavaScriptのルーターがURLを解釈して画面を切り替えています。

つまりSPAのルーティングとは「ブラウザ側の仮想ルーティング」です。サーバはURLの意味を理解していません。

なぜ404が起きるのか(本質)

ブラウザからサーバへは、次のようなHTTPリクエストが送られます。

GET /dashboard HTTP/1.1
Host: example.com

ここでサーバは「/dashboardというファイルをください」と解釈します。しかしそのファイルは存在しません。

結果:

404 Not Found

これは正しい動作です。サーバはルーターを知らないからです。SPAのルーティングはJavaScriptが起動して初めて機能します。しかし404が返された時点で、JavaScriptはまだ実行されていません。

ここが404問題の本質です。

なぜリンククリックでは動くのか

リンククリック時に動く理由は、サーバへリクエストが行われていないからです。

SPAのリンクは通常のaタグではありません。ルーターがクリックイベントを横取りしています。

処理の流れ:

  • クリック
  • JSがイベントを捕捉
  • pushState()でURLを書き換え
  • 画面を描画

つまりサーバに問い合わせていません。だから404にならないのです。

一方でリロードや直接アクセスでは、必ずサーバへHTTPリクエストが送信されます。その時点でSPAはまだ存在していません。

必要になるサーバ設定の正体

解決策は「全URLでindex.htmlを返す」ことです。存在しないパスでも404にせず、SPAの入口を返します。

Apacheの場合:

RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]

Nginxの場合:

location / {
  try_files $uri $uri/ /index.html;
}

これで /dashboard にアクセスしても index.html が返ります。その後JavaScriptが起動し、ルーターが画面を表示します。

つまりサーバ設定は「SPAの代わりに入口を返す」ために必要です。

よくある誤解:historyモードが悪い?

Vue RouterやReact Routerのhistoryモードを使うと404が出ると言われますが、モードの問題ではありません。

  • hashモード:/#/dashboard
  • historyモード:/dashboard

hashはサーバに送られないため404にならないだけです。本質的な解決ではありません。

historyモードはURLを本来の形に戻しただけです。問題を表面化させただけとも言えます。

もう一つの問題:APIの404を壊す危険

ここで注意が必要です。全てのURLをindex.htmlへリダイレクトすると、APIまで壊れます。

例:

  • /api/users

これもindex.htmlが返ってしまいます。結果、フロントエンドはJSONの代わりにHTMLを受け取り、不可解なエラーになります。

そのためAPIパスは除外します。

location /api/ {
  proxy_pass http://backend;
}

location / {
  try_files $uri $uri/ /index.html;
}

SPA設定で一番多いトラブルはここです。404対策をしたらAPIが壊れる、という現象です。

SEOやSSRとの関係

この問題はSSRでは発生しません。理由は簡単です。SSRではサーバ自身がルーティングを理解しているからです。

SPAは「ブラウザがルーター」、SSRは「サーバがルーター」です。

つまりSPAの404問題は、SPAが間違っているのではなく「HTTPはファイル取得のプロトコル」であることとのズレから生まれています。

結局どう考えるべきか

SPAを公開するというのは、フロントエンドのアプリを配布しているのではありません。サーバに仮想的な入口を用意するという作業を含みます。

SPAはブラウザの中にWebサイトを作る技術です。しかしユーザーは最初に必ずサーバへアクセスします。この1回目の橋渡しを担当するのがサーバ設定です。

404が出るのは設定ミスではなく、HTTPが正常に動いている証拠です。SPAはその挙動を前提として設計されています。サーバとブラウザの役割の境界を理解すると、この問題は不具合ではなく「設計上の必然」に見えてきます。