SPA の仕組みを書いた記事が見当たらなかったので、自分の理解を書いてみます。
普通のページ遷移
SPA じゃない普通の Web ページの場合、まず https://example.com/
にアクセスすると GET /
な HTTP リクエストが飛んで、ドキュメントルートにある index.html
(静的ページの場合) が返ります。
ここで <a href="/about/">
なリンクを踏むと、https://example.com/about/
に遷移することになり、GET /about/
な HTTP リクエストが飛んでドキュメントルートの about
ディレクトリの中の index.html
(以下 /about/index.html
のように書きます) が返ることになります。
静的 Web サイトのファイル構成
% tree /var/www/html
/var/www/html
├── about
│ └── index.html # GET /about/ で返るやつ
└── index.html # GET / で返るやつ
SPA のページ遷移
SPA の場合、例えば react-router の <Link to="/about/">
を踏むと、ブラウサの Hitsory API を使って、実際にページ遷移を行う代わりに、仮想的に https://example.com/about/
へのページ遷移が行われたことにします。このため、GET /about/
な HTTP リクエストは発生しません。
また、その後ブラウザの戻る・進むボタンが押された場合も、ページ遷移は発生せずに JavaScript のイベントが通知されて、router が表示コンポーネントの切り替えを行います。
SPA のファイル構成
% tree /var/www/html
/var/www/html
├── bundle.js
└── index.html
SPA でリロードした場合
問題は、about ページを表示した状態でリロードした場合です。この場合、現在位置は https://example.com/about/
なので、ブラウザは GET /about/
な HTTP リクエストを行います。この際、Web サーバー側に /about/index.html
が存在しなければ 404 エラーになってしまいます。
これを防ぐため、Web サーバー側では /about/
などの <Link to="〜">
でリンクされる可能性のある URL (または、ファイルが存在しない URL すべて) のリクエストに対して、/index.html
の内容を返すような設定にする必要があります。開発中は webpack-dev-server なら historyApiFallback で簡単に設定できます (create-react-app した場合は勝手にそうなってます) が、ビルドしてデプロイする場合は自分で Web サーバーを設定しないといけません。
また、index.html
から bundle.js
などに対して相対パスでアクセスしている場合、/about/bundle.js
へのアクセスも発生してしまうので、こちらの対応 (絶対パスでアクセスするか <base href="〜">
を設定するなど) も必要です。