事象
Reactで作成したWebアプリをAWS S3にデプロイしたところ、TOPページ以外のページでリロードした際に、404 Not Foundになりました。
404 Not Found
・ Code: NoSuchKey
・ Message: The specified key does not exist.
・ Key: ******
・ RequestId: ****************
・ HostId: ****************
ルーティングにはreact-router-dom
のBrowserRouter
を使用しています。
原因
今回Reactで作成したWebアプリはSingle Page Application(SPA)です。
React Routerはクライアントサイドでのルーティングを提供するもので、URLに応じて新たにUIをレンダリングすることで、別ページへ遷移したかのように見せかけます。
S3上にはindex.html以外のページは存在しないため、リロードすると、S3がそのURLの先に置かれたファイルを探してしまい、404エラーが発生します。
BrowserRouter
の代わりにHashRouter
を使うことでも解決できるようですが、URLに#
が入るのは綺麗ではありません。
解決策
方法1:エラードキュメントにindex.htmlを登録する
S3 > 該当のバケット > プロパティ > 静的ウェブサイトホスティング
の編集
を選択します。
エラードキュメントにindex.html
と入力して変更の保存
をクリックします。
この設定によって、エラーが発生した場合にindex.htmlが返されるようになります。
BrouserRouter
はブラウザのアドレスバーに現在位置を保存し、ブラウザの履歴スタックを利用して遷移を実現しています。
index.htmlが返されても、アドレスバーに入力されているURLは変わらないため、リロード直前に表示されていたUIがレンダリングされます。
この方法は手軽な上、見かけ上は問題なく動作します。しかし、デベロッパーツールのコンソールを見てみると、依然としてリロード時に404エラーが発生しています。
そこで、次の方法も試してみました。
方法2:CloudFrontのカスタムエラーレスポンスを使用する
まず、CloudFrontのディストリビューションを作成します。
オリジンドメインには該当のS3バケットを指定してください。入力ボックスを選択すると、S3バケットの候補が表示されるので、該当のバケットを選択の後、Webサイトのエンドポイントを使用
をクリックします。
デフォルトルートオブジェクトにはindex.html
を入力します。
他の項目は適宜設定してください。
ディストリビューションの作成
をクリックします。デプロイが終了するまでしばらく時間がかかります。
続いて、該当のディストリビューション > エラーページ > カスタムエラーレスポンスを作成
と進みます。以下の図のように、404エラーに対してレスポンスコード200と/index.html
を返すように設定します。
こうすることで、方法1のようにリロード時に404エラーが発生することがなくなります。
一方で、次のような問題点があるようです。
- エラーレスポンスを書き換えるため、ブラウザ側でエラーハンドリングを実装しようとすると機能しなくなる
- SPAと従来のウェブアプリケーションのハイブリッド構成の場合、意図しないレスポンスになることがある
どの方法にも一長一短がありますね。
参考