DEV Community

うぱるぱ
うぱるぱ

Posted on • Originally published at archelon-inc.jp

CloudFront パスパターンで「ルートだけ」を特定するテクニック

CloudFront パスパターンで「ルートだけ」を特定するテクニック

はじめに

AWS CloudFrontでWebサイトを構築する際、「ルート(/)だけを特別なオリジンに向けたい、あるいは特別な処理をさせたい」という要件に直面することがあります。

しかし、CloudFrontのパスパターン設定には「/ のみ」をピンポイントで指定する記法がなく、通常は Default Behavior (*) を使うしかありません。これではルート以外のアセット(JS/CSS/画像)まで同じ設定に巻き込まれてしまい、キャッシュ戦略やオリジンの分離が難しくなります。

本記事では、CloudFrontのワイルドカード仕様を逆手に取った、/?* パターンを使ってルートを浮かび上がらせるテクニックをご紹介します。

背景:CloudFront パスパターンの制約

CloudFrontのキャッシュビヘイビアでは、パスパターンを使ってルーティングを制御しますが、以下の制約があります。

  • 正規表現は使えない
  • 使用可能なワイルドカード:
    • *: 0文字以上の任意の文字列にマッチ
    • ?: 正確に1文字にマッチ
  • パターンの評価: 設定の上(優先度が高い順)から評価され、最初にマッチしたものが適用される

この「正規表現が使えない」という制約の中で、「ルート以外すべて」を表現する方法が鍵となります。

解決策:/?* パターンの正体

ここで活用するのが、? ワイルドカードの「正確に1文字」という仕様です。

パターンの意味

/?* というパターンは、以下のように解釈されます。

  1. / : スラッシュ(固定)
  2. ? : 正確に1文字(必須)
  3. * : 0文字以上の任意の文字列

つまり、/?*「スラッシュの後に少なくとも1文字以上あるパス」 にマッチします。

💡 ここがポイント

このパターンを優先度「高」で設定することで、Default Behavior (*) にはルートリクエスト(/)だけを落とし込むことが可能になります。

マッチングの結果

パス マッチ判定 理由
/index.html ✅ マッチ / + i + ndex.html(1文字以上の継続あり)
/main.js ✅ マッチ / + m + ain.js(1文字以上の継続あり)
/assets/style.css ✅ マッチ スラッシュの後に「1文字以上」が存在する
/ 不適合 スラッシュの後に文字がゼロ個のため

実用的なユースケース:SSR + 静的アセットの分離

最も汎用的な活用例は、AstroNext.js などを用いた「SSR(動的生成)と静的配信のハイブリッド構成」です。

  • / (ルート): 最新記事のリストやSEO用のメタタグを動的に生成したい(Lambda / App Runner 等の SSR)
  • それ以外の全パス (/assets/*, /favicon.ico 等): S3から直接、高速かつ安価に配信したい

通常なら拡張子ごとにビヘイビアを定義する必要がありますが、/?* なら一撃で分離できます。

CDKでの実装例

このテクニックを用いると、インフラ構成コード(CDK)も非常にシンプルになります。

import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';
import * as origins from 'aws-cdk-lib/aws-cloudfront-origins';

const distribution = new cloudfront.Distribution(this, 'MyHybridDist', {
  // 1. Default (*) ビヘイビア:優先度が最も低い
  // ここには / だけが落ちてくるため、SSRサーバー(動的オリジン)へ向ける
  defaultBehavior: {
    origin: new origins.HttpOrigin('ssr-api.example.com'), 
    cachePolicy: cloudfront.CachePolicy.CACHING_DISABLED, // SSRは基本キャッシュなし
    viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
  },
  additionalBehaviors: {
    // 2. ルート以外のすべて (/?*):優先度を高く設定
    // アセット類をすべて S3 から配信する
    '/?*': {
      origin: origins.S3BucketOrigin.withOriginAccessIdentity(assetBucket),
      cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED, // 静的ファイルは強力にキャッシュ
      viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

この手法のメリット

  1. 拡張子の列挙が不要: *.js, *.css, *.webp ...と延々と設定を増やす必要がありません。
  2. 責務の分離 (Separation of Concerns): 「動的なトップページ」と「不変の静的アセット」という、性質の異なるリソースをパスのレベルで完全に分離できます。
  3. キャッシュ戦略の最適化: ルートHTMLには no-cache を、アセット類には長期間のキャッシュを、といった切り分けが一行のパターン定義で完結します。
  4. メンテナンス性: 新しいファイル形式を追加しても、CloudFrontの設定変更は不要です。

⚠️ 注意点:CloudFront Functions との組み合わせ

このテクニックを使用する際、CloudFront Functions でパスの書き換え(Rewriting)を行う場合は注意が必要です。

CloudFrontの仕様上、Function内で request.uri を書き換えても、キャッシュビヘイビアの再評価(再マッチング)は行われません。

例えば、Default Behavior (*) に落ちてきたリクエストを Function で /index.html に書き換えたとしても、そのリクエストが /?* ビヘイビアに移動することはありません。あくまで「最初にマッチしたビヘイビアの設定」で処理が継続されます。

まとめ

CloudFrontの ? ワイルドカードは地味な存在ですが、「正確に1文字」という厳密さを利用することで、標準機能だけでは難しい「パスの否定論理」を組み立てることができます。

「ルートだけを特別扱いしたい」という要件は、SSR構成、多言語リダイレクト、あるいはメンテナンス表示など、実務で頻繁に発生します。この /?* テクニックを、シンプルかつ堅牢なインフラ設計の引き出しとして持っておくと非常に便利です。

参考資料:

Top comments (0)