SPAのクライアントサイドルーティングをCloudFront Functionsで最適化する
はじめに
SPAをAWS上でホスティングする場合、クライアントサイドルーティングに関する問題が発生することがあります。本記事では、CloudFront Functionsを使用してこの問題を効率的に解決する方法を紹介します。
CloudFront Functionsは、エッジロケーションで実行される軽量なJavaScript関数で、以下の特徴があります。
- 平均1ms未満の超低レイテンシー実行
- グローバルに分散されたエッジロケーションでの処理
- サーバーレスで自動スケーリング
- 単純な処理に最適化された軽量設計
SPAのルーティング問題の概要
SPAをCloudFrontとS3で配信する際に発生する主な問題は、次のようなケースでの404エラーです。
/products/123
のようなサブパスに直接アクセスした場合- SPAページのリロード時
- ブックマークからアクセスした場合
これは、SPAのルーティングがクライアントサイドJavaScriptで処理される一方、S3にはその実際のパスに対応するファイルが存在しないためです。例えば、/products/123
というリクエストがS3に届いた場合、そのパスのファイルは実際には存在しないため、エラーになります。
解決アプローチの比較
SPAのルーティング問題には主に2つの解決策があります。
1. CloudFront Functionsによる解決(推奨)
特徴:
- エッジでのリクエスト書き換えによる高速処理
- 複数オリジン環境での選択的適用が可能
- レイテンシーが低く、コスト効率が高い
2. カスタムエラーページによる解決
特徴:
- 設定は比較的簡単
- ただし、オリジンへのリクエスト→エラー→リダイレクトという多段処理が発生
- 複数オリジン環境では問題が生じる可能性がある(APIからの正常なエラーコードも変換されるため)
パフォーマンスとコスト効率を重視する場合は、CloudFront Functionsによる解決が明らかに優れています。
CloudFront Functionsの実装例
以下は、SPAのルーティング問題を解決するための実用的な実装例です。この関数は、拡張子のないパスや特定のパターンに対して適切な処理を行います。
function handler(event) {
var request = event.request;
var uri = request.uri;
// 静的アセット(拡張子を持つファイル)はそのまま処理
if (uri.match(/\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot|json|xml)$/)) {
return request;
}
// APIパスなど特定のパスを除外(必要に応じてカスタマイズ)
// 同じディストリビューションでAPIも配信している場合など
if (uri.startsWith('/api/')) {
return request;
}
// 以下の条件でindex.htmlにリダイレクト:
// 1. ファイル拡張子がない場合(/about, /products/123など)
// 2. パスがスラッシュで終わる場合(/about/, /products/など)
if (!uri.includes('.') || uri.endsWith('/')) {
// パターン1: URIがスラッシュで終わる場合
if (uri.endsWith('/')) {
request.uri += 'index.html';
}
// パターン2: 拡張子を持たないパス
else if (!uri.includes('.')) {
request.uri += '/index.html';
}
}
return request;
}
制限事項と注意点
CloudFront Functionsには以下の制限があります。
- 関数の持続時間:サブミリ秒(1ms未満)
- メモリ:2MB
- 関数サイズ:最大10KB
- JavaScript ES 5.1準拠(一部ES6機能はサポート)
- 外部リソースへのアクセス不可
まとめ
CloudFront Functionsを使用したSPAルーティングの解決方法は、以下の理由でカスタムエラーページ方式より優れています。
- 超低レイテンシー処理によるパフォーマンス向上
- オリジンへのリクエスト削減によるコスト最適化
- 複数オリジン環境での選択的適用による柔軟性
- シンプルでわかりやすい実装
特に複合的なアーキテクチャ(フロントエンドとAPIを同じディストリビューションで提供する場合)では、CloudFront Functionsの使用が推奨されます。