SPA を開発する際に必須のタスクの一つとして、History API
のフォールバック(以下、単に「フォールバック」と記述する)がある。
この事象について掻い摘んで説明すると、SPA においては URL と HTML ファイルが一対一になっていないので、それに伴う対応を行うこと。
SPA ではその名の通りページは一枚しかなく、URL の管理や表示するコンテンツの切り替えは、ルーティングライブラリが行っている。
例えば、唯一のページを返す URL がhttp://example.com/
である場合、その URL が返す HTML ファイルが JS ファイルを読み込み、その JS ファイルがルーティングライブラリによるプログラムを実行することで、http://example.com/foo
やhttp://example.com/bar
といった URL が有効になる。
問題となるのが、いきなりhttp://example.com/foo
などの URL にアクセスされた場合。他のサイトからのリンクであったり、ページのリロードなどで、発生する。
この場合、http://example.com/foo
に対応する HTML ファイルは存在しないため、404
となってしまう。
そのため、http://example.com/foo
やhttp://example.com/bar
へのアクセスがあった場合は、http://example.com/
が対応している HTML ファイルを返すようにしないといけない。
そうすることで、必要な HTML ファイルと JS ファイルが読み込まれ、アクセスのあった URL に対応するコンテンツを表示させることが出来る。
開発環境でよく使われているwebpack-dev-server
では、historyApiFallback
オプションを使うことで、対応できる。
本番環境ではサーバーの設定が必要。
Vue
と組み合わせて使うルーティングライブラリVue Router
のドキュメントで、いくつかの例が簡単に紹介されている。
HTML5 History モード | Vue Router
この記事では、SPA の配信にCloudFront
を使う場合にどうやってフォールバックをするのか、書いていく。
Error Pages を使う?
CloudFront
にはError Pages
という設定項目があり、403
や404
といったステータスコード毎に、レスポンスするページを設定できる。
これを使ってフォールバックを設定することが出来る。
先程の例なら、404
が発生したときは/index.html
を返すように設定すればよい。
だがこの方法だと、起点となるページが複数あるケースに対応できない。
SPA の規模によっては、起点となるページが一つだとは限らない。
そのようなときは、レスポンスしたいページは複数になる。
例えば、http://example.com/user/*
にアクセスがあったときはhttp://example.com/user/index.html
を、http://example.com/product/*
にアクセスがあったときはhttp://example.com/product/index.html
を返したいとする。
Error Pages
では、このようなニーズに応えることは出来ない。一つのステータスコードに対して、一つのページしか設定できないから。
複数のフォールバックを設定するには、Lambda@Edge
を使う。
Lambda@Edge で URL をリライトする
Lambda@Edge
は、CloudFront
のエッジロケーションでLambda
を実行するサービス。
これを利用して、リクエストのあった URL に応じてリライトすることで、複数のフォールバックを設定できる。
IAMの設定
Lambda
を利用するにはIAM
ロールが必要なので、作成する。
まず、以下の内容のIAM
ポリシーを作成する。
CloudWatch Logs
にログを出力することになるので、その権限が必要。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "arn:aws:logs:*:*:*" } ] }
新しくロールを作り、上記のポリシーを付与する。
そして、「信頼されたエンティティ」としてlambda.amazonaws.com
とedgelambda.amazonaws.com
を指定する。
これでIAM
の設定は完了。
関数の作成
Lambda
で関数を作成する。
Lambda@Edge
を使うには、リージョンを「バージニア北部」に、ランタイムをNode.js 8.10
かNode.js 6.10
に設定しておく必要がある。
ロールは、先程作成したものを使う。
コードを書く
先程の例だと、以下のように書けばいい。
そんなに難しい内容ではないのだが、ポイントとしては以下。
- 第一引数の
event
の中から、リクエストされた URI を取得する - その URI に応じて処理を変えたり、URI を書き換えたりする
console.log
を実行すると、その内容がCloudWatch
に保存される
exports.handler = (event, context, callback) => { const {request} = event.Records[0].cf; const currentUri = request.uri; // ドットを含むURIは、アセットへのアクセスとみなし、リライトしない if (currentUri.indexOf('.') !== -1) { console.log(`Don't rewrite. Uri is ${currentUri}`); return callback(null, request); } let newUri = currentUri; switch (true) { case /^\/user/.test(currentUri): newUri = '/user/index.html'; break; case /^\/product/.test(currentUri): newUri = '/product/index.html'; break; default: } console.log(`Old URI: ${currentUri}`); console.log(`New URI: ${newUri}`); request.uri = newUri; return callback(null, request); };
デプロイ
コードを保存したら、デプロイする。
ページ上部の「アクション」ボタンから「Lambda@Edge へのデプロイ」を選択すればよい。
今回のケースでは「CloudFront イベント」は「オリジンリクエスト」を選ぶ。
Lambda@Edge の解除
設定したLambda@Edge
の解除は、Lambda
の画面からは出来ない。
CloudFront
のディストリビューションの設定画面からBehaviors
を選び、Lambda Function Associations
を編集することで解除できる。
そうすると、Lambda
の画面から、関数を削除することも出来るようになる。