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のドキュメントを確認すると答えはありました。
どうやら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利用時のフォールバックを設定する
上記のページに以下のケースでのフォールバックは設定例があるのでこのまま使えそうです。
- 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つくることなんかせず、おとなしくフレームワーク使うことをオススメします。