History APIでルーティングしてる時の存在しないページへのフォールバックってどうすんの?


JavaScriptでSPAのルーティングを実装するためHistory APIを使っていたところ、操作した後のパスでリロードをした時にこんな画面になりました。

404です(WARNINGは拡張機能起因のものなのでスルーします)。
ちなみにサーバーはVSCodeのLive Server Extensionを利用しています。

なぜ?と思う前に冷静にこのアプリのディレクトリ構成を確認します。

|-- index.html
|-- style.css
|-- js
    |-- main.js
    |-- router.js

よく見れば当たり前の話です。newPost.htmlなんて存在せず、index.htmlしかないので、ルートとなるhttp://127.0.0.1:5500/以外はそもそも存在しません。そりゃ404にもなります。

あれ、でもVueとかNuxt使ってる時に直接このパス見に行ってもページ表示できたよな?あれどうなってんだと思い調べてみることにしました。

Vue Routerのドキュメントをちゃんと読む

Vueのルーティングを司っているVue Routerのドキュメントを確認すると答えはありました。

HTML5 History モード

どうやらhashとhistoryという2つのモードがあるみたいです。

hashモード

vue-router のデフォルトは hash モード です - 完全な URL を hash を使ってシミュレートし、 URL が変更された時にページのリロードが起きません。

hashモードってアレだ!URLがhttps://example.com/#/userみたいに#が間に含まれてるやつだ。思い出した。昔Angular.jsで開発してたときもHistory APIがどうのこうのってこのhashモードを使ってました。

hashモードが動く理由は、URLのアンカー#の動きを利用してhttps://example.com/#/userの例で言えば実際はhttps://example.comを見に行くという理解を得ました。となるとその中で更にアンカーでのスクロールを実装したいときどうなるんだろう?https://example.com/#/user#profileみたいなケース。……これは別の機会に調査します。

historyモード

history モードを使用する時は、URL は "普通" に見えます e.g. http://oursite.com/user/id。美しいですね!

しかしながら一点問題があります。シングルページのクライアントサイドアプリケーションなので、適切なサーバーの設定をしないと、ユーザーがブラウザで直接 http://oursite.com/user/id にアクセスした場合に 404 エラーが発生します。

まんまこの問題ですね。

心配する必要はありません。この問題を直すためには、単純な catch-all フォールバックのためのルートをサーバー側で追加するだけです。もし URL がどの静的なアセットにもマッチしなかった時はあなたのアプリケーションが動作しているのと同じ index.html ページで受け付けましょう。これも美しいですね!

なるほど!フロント側ではやはりどう足掻いても駄目みたいですね。Vue CLIやらNuxtでの開発は便利ですが、こういうのも隠蔽して裏側でやってくれていたということっぽい。たまにはフレームワーク抜きでJavaScript触るのも学びがあって良いですね。

History API利用時のフォールバックを設定する

HTML5 History モード

上記のページに以下のケースでのフォールバックは設定例があるのでこのまま使えそうです。

  • Apache
  • nginx
  • Node.js
  • Express
  • IIS
  • Cady
  • Firebase

これ以外で今回私が個人的に使ってるLive Server ExtensionとNetlifyでの実装も調べたのでここに残しておきます。

Liver Server Extension

VS Codeの設定ファイルに以下を追記します。

{
  "liveServer.settings.file": "index.html"
}

念の為、個人設定ではなくワークスペース単位での設定をオススメします。

Netlify

_redirectsファイルに以下を追記します。

/*   /index.html   200

こによりどのパスでアクセスしてもindex.htmlが参照されます。

サーバーにフォールバックを設定した際の注意点

サーバーに上記のフォールバックを設定した時点で、あらゆるアクセスはindex.htmlにリダイレクトされます。つまり、以下のようなディレクトリ構成で:

|-- index.html
|-- user.html
|-- item.html

https://example.com/userにアクセスしようが、https://example.com/itemにアクセスしようが,
実態はhttps://example.com/にアクセスがリダイレクトされます(ただしURLは/user//itemがついた状態)。なので、以下のようなSPAを前提とした実装が必要になります。

  • 完全なるルーティングの制御
    • ページロード時(初期表示時)、現在のパスを取得し対応するコンポーネントを表示する
    • 対応するコンポーネントが存在しないパスを表示時、404を表すページを表示する

完全なるルーティングの制御の実装はなかなか骨が折れます。Historyの状態をpush/popする度にコンポーネントの描画を切り替える実装が必要になってきます。VueやReactなどのSPA作れるフレームワークを使う時はセットとなるルーターのライブラリを使いましょう。勉強以外の目的で素のJavaScriptでSPAつくることなんかせず、おとなしくフレームワーク使うことをオススメします。