CloudFront+S3環境上のSPA(Angular)で「/」以外のURLでリロードした場合に403(access denied)エラーとなる時の対処法

SPAをS3+CloudFront環境上にデプロイし、配信することはよくあるパターンと思います。Angularで実装したSPAを配信したとき、ページリロードしたときに403(access denied)エラーとなった際の対処法をご紹介します。
2018.07.06

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

どうも!大阪オフィスの西村祐二です。

SPAをホスティングする際にCloudFront+S3環境上にデプロイして、配信することはよくあるパターンと思います。

私もAngularでSPAを開発することがよくあります。初めてCloudFront+S3環境で配信したときに、「/」以外のURLでリロードした場合に403(access denied)エラーとなる現象に遭遇して、ハマってしまいました。今回この対応策をご紹介したいと思います。

エラーを再現

ハマった現象を再現するように環境を構築していきます。対応策を早く知りたいかたは下の対応策の項目まで飛ばして大丈夫です。

CloudFront+S3の環境構築

CloudFront+S3の環境は下記ブログにCloudFormationのテンプレートが記載されているので、それを使ってサクッと構築しましょう。感謝!

CloudFormation で OAI を使った CloudFront + S3 の静的コンテンツ配信インフラを作る

AngularでSPAを実装

環境

  • Angular CLI: 6.0.8
  • Node: 8.11.3
  • OS: darwin x64
  • Angular: 6.0.7

実装

CLIを使って雛形を作成します。

$ ng new test-app --routing

/testにルーティングしたときに表示するtestコンポーネントを作成します。

$ ng g c test

URLが/testのときに、testコンポーネントが表示されるようにルーティングします。app-routing.module.tsを編集していきます。

app-routing.module.ts

import { TestComponent } from './test/test.component';
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [{ path: 'test', component: TestComponent }];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}

動作確認

ローカルで動作確認します。

$ ng serve

http://localhost:4300/testにアクセスしてみます。下記画像のように、下の方に、test works!と表示されていたらokです。

CloudFront+S3の環境構築にデプロイ

下記コマンドで最適化した形でjs,css,htmlを吐き出してくれます。

$ ng build --prod

生成されたファイルを確認してみます。

$ ls dist/test-app
3rdpartylicenses.txt polyfills.2f4a59095805af02bd79.js
favicon.ico runtime.a66f828dca56eeb90e02.js
index.html styles.34c57ab7888ec1573f9c.css
main.80d1aff9569d95f322a4.js

デプロイはAWS CLIでS3に先程生成されたファイルを配置することでデプロイできます。 --deleteとすることで、ローカルファイル以外のファイルがS3バケットにある場合は削除するオプションになります。

$ aws s3 sync dist/test-app s3://test-spa-assetsbucket-xxxxxxxx --delete

これで、準備はできました。cloudfrontのurlにアクセスし、/testに移動したあとにリロードすると下記のような表示がされます。

対応策

エラーの原因として対応するファイルがないため、ブラウザリロードすると 403 エラーになってしまうようです。 そのため、CloudFrontのカスタムエラーレスポンスで、403のときに/index.html(今回は「/」)へ転送するよう設定することでこの現象を回避することができます。

設定としては、下記画像のように設定することでリロードしてもエラーとならずページが表示されます。

さいごに

いかがだったでしょうか。

はじめて、Angularで実装したSPAをS3+CloudFront環境上で配信したときにハマったことと、その対応策をご紹介しました。

誰かの参考になれば幸いです。