CloudFrontのDefault Root Objectはサブディレクトリでは効かない

  • AWS
  • CloudFront
  • CDK

この記事は、構成と文章の整理にAIを使っています。
あくまで読みやすくするための補助で、内容そのものをAIにすべて任せて書いているわけではありませんが、ところどころ相談しながら整えています。

何が起きたか

CloudFrontのオリジンにS3のRESTエンドポイント(example-bucket.s3.amazonaws.comのような形)を使っている構成で、/hoge/にアクセスしたときにhoge/index.htmlを返したかった。

CloudFrontには「デフォルトルートオブジェクト」という設定があるが、これはディストリビューションのルートパス(/)に対してしか効かない。/hoge/のようなサブディレクトリには適用されない。

S3ウェブサイトホスティングを使わない前提

S3の「静的ウェブサイトホスティング」エンドポイントをオリジンにすれば、S3側のディレクトリインデックス機能で/hoge/ → hoge/index.htmlの解決がそのまま行われる。

ただし今回はRESTエンドポイントを使う前提(OAC経由でのアクセス制御をしたいなど、ウェブサイトホスティングエンドポイントだと都合が悪い理由がある場合)だったので、この方法は採らなかった。

CloudFront Functionsで書き換える

RESTエンドポイントオリジンのままサブディレクトリのindex.html解決をしたい場合、Lambda@EdgeかCloudFront Functionsでリクエストのuriを書き換える必要がある。今回はレイテンシと料金の観点でCloudFront Functionsを選んだ。

CloudFront FunctionsはJavaScriptで書くが、ランタイムはES5.1準拠(cloudfront-js-1.0)。const/letもアロー関数も使えないので、書くとしたらこんな感じになる。

function handler(event) {
var request = event.request;
if (request.uri.charAt(request.uri.length - 1) === '/') {
request.uri = request.uri + 'index.html';
}
return request;
}

TypeScriptで書いてES5にコンパイルする

素のJSでもいいが、TypeScriptで書いてビルド時にES5へトランスパイルし、そのJSをCDKのFunctionCode.fromInlineに読み込ませる方法を試した。

src/myCloudFrontFunction.ts
export function handler(event: any) {
const request = event.request;
if (request.uri.endsWith('/')) {
request.uri = request.uri + 'index.html';
}
return request;
}

tsconfig.jsonはtargetをes5にする。

{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"outDir": "dist",
"strict": true
}
}

コンパイル後のJSファイルを読み込んでCDKに渡す。

import * as fs from 'fs';
import * as path from 'path';
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';
import * as origins from 'aws-cdk-lib/aws-cloudfront-origins';
const functionCode = fs.readFileSync(
path.join(__dirname, '../dist/myCloudFrontFunction.js'),
'utf8',
);
const cfFunction = new cloudfront.Function(this, 'MyCloudFrontFunction', {
code: cloudfront.FunctionCode.fromInline(functionCode),
});
new cloudfront.Distribution(this, 'MyDistribution', {
defaultBehavior: {
origin: new origins.HttpOrigin('example.com'),
functionAssociations: [
{
function: cfFunction,
eventType: cloudfront.FunctionEventType.VIEWER_REQUEST,
},
],
},
});

projenでの自動生成は無い

projenはhoge.lambda.tsのようなファイル名規則からLambda関数を自動生成する仕組みを持っているが、CloudFront Functionについては同様の自動生成には対応していない(2025年3月時点、projenのドキュメント上で確認できる範囲)。そのため、上記のように自前でビルドスクリプトを組んでJSを読み込ませる形にした。

現状の制約

  • CloudFront FunctionsはES5.1準拠。const/letは使えないので、TypeScript側でも実質ES5相当の書き方に寄せておいた方がトランスパイル後の差分が少ない
  • コードサイズに上限がある
  • ビルド出力のJSファイルをCDKスタック側でfs.readFileSyncしているので、CDK synth前にビルドが完了している必要がある

追記(2026-07-27)

この記事の内容は古い。実際にはcloudfront-js-2.0というランタイムが2024年4月から存在していて、const/letやテンプレートリテラルなどES6以降の一部機能も使える。当時ES5.1固定だと思い込んでいたのは誤り。

詳しくはCloudFront FunctionsはES5.1固定じゃなかった、cloudfront-js-2.0でES6以降の一部にも対応してるに書いた。