From 269561079b55947881aebd113e4c20fdba4d0a61 Mon Sep 17 00:00:00 2001 From: r-ikeda Date: Wed, 6 May 2026 10:00:56 +0900 Subject: [PATCH] =?UTF-8?q?CloudFront=20Function=20=E3=81=A7=E3=82=B5?= =?UTF-8?q?=E3=83=96=E3=83=91=E3=82=B9=E3=83=AA=E3=83=A9=E3=82=A4=E3=83=88?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit S3 (REST origin + OAC) の DefaultRootObject はバケットルートにしか効かず、 /company/ などのリロードで 403 AccessDenied が返っていた。 viewer-request で /foo/ → /foo/index.html にリライトする CloudFront Function ic-gr-url-rewrite を導入し、.net ディストリビューション (EHFD30ZL5XZ0U) に 紐付けて解消。関数本体とデプロイ手順を infra/cloudfront/ に追加し、 CLAUDE.md の関連節を実態に合わせて修正。 Co-Authored-By: Claude Opus 4.7 (1M context) --- CLAUDE.md | 14 ++++++-- infra/cloudfront/README.md | 59 +++++++++++++++++++++++++++++++++ infra/cloudfront/url-rewrite.js | 26 +++++++++++++++ 3 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 infra/cloudfront/README.md create mode 100644 infra/cloudfront/url-rewrite.js diff --git a/CLAUDE.md b/CLAUDE.md index 5f53cc4..ca80d9f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -59,9 +59,19 @@ aws --profile ic-gr cloudfront create-invalidation \ `.com` ドメイン側の CloudFront には今回のデプロイでは invalidation を打っていないため、`*.ic-gr.com` でも即時反映したい場合は手動で `--distribution-id E3CUYP7CXV3V06` の invalidation を実行する。 -### SPA 直リンク問題は解消済み +### サブパス直リンク / リロード対応 -旧 CRA では CloudFront に Custom Error Response 未設定だったため `/overview` などのリロードで 403 を返していたが、Next.js の static export + `trailingSlash: true` で各ルートが `out/overview/index.html` として生成されるため、ハードリロードでも 200 が返る。 +`trailingSlash: true` で `out//index.html` は生成されるが、CloudFront の `DefaultRootObject` はバケットルートにしか効かないため、それだけでは `/company/` などへのリロードで `403 AccessDenied` が返る (S3 REST origin + OAC 構成のため、サブパスのキーが見つからない → 非公開バケットなので 403)。 + +これを解消するため CloudFront Function `ic-gr-url-rewrite` を viewer-request として紐付けている。コード本体は `infra/cloudfront/url-rewrite.js`、デプロイ手順は同ディレクトリ `README.md` を参照。 + +``` +/company/ → /company/index.html +/company → /company/index.html +/foo.ext → そのまま (拡張子があるリクエストはリライトしない) +``` + +現在 `.net` (`EHFD30ZL5XZ0U`) のみ紐付け済み。`.com` (`E3CUYP7CXV3V06`) も同じ症状になるため、必要なら同じ関数 (`arn:aws:cloudfront::058264181659:function/ic-gr-url-rewrite`) を viewer-request として紐付ければよい。 ## ディレクトリ構成 diff --git a/infra/cloudfront/README.md b/infra/cloudfront/README.md new file mode 100644 index 0000000..1fcab3a --- /dev/null +++ b/infra/cloudfront/README.md @@ -0,0 +1,59 @@ +# CloudFront Function — `url-rewrite` + +Next.js static export の `out//index.html` を、CloudFront のサブパスリクエスト +(例: `/company/`, `/overview/`)に対して正しく解決させるための viewer-request 関数。 + +S3 (REST origin + OAC) では `DefaultRootObject` がバケットルートにしか効かないため、 +これが無いとサブパスへの直リンク/リロードで `403 AccessDenied` になる。 + +## 関連リソース + +- 関数名: `ic-gr-url-rewrite` +- ランタイム: `cloudfront-js-2.0` +- 紐付けイベント: viewer-request +- 対象ディストリビューション: + - `EHFD30ZL5XZ0U` (`*.ic-gr.net`) + - `E3CUYP7CXV3V06` (`*.ic-gr.com`) ※同じ S3 を origin にしているため同じ関数を共用 + +## 初回デプロイ(CLI 例) + +```bash +PROFILE=ic-gr +NAME=ic-gr-url-rewrite + +# 1. 関数作成 +aws --profile $PROFILE cloudfront create-function \ + --name $NAME \ + --function-config Comment="Rewrite /foo/ to /foo/index.html for Next.js static export",Runtime=cloudfront-js-2.0 \ + --function-code fileb://url-rewrite.js + +# 2. publish(DEVELOPMENT → LIVE) +ETAG=$(aws --profile $PROFILE cloudfront describe-function --name $NAME --query 'ETag' --output text) +aws --profile $PROFILE cloudfront publish-function --name $NAME --if-match "$ETAG" + +# 3. 各ディストリビューションの DefaultCacheBehavior.FunctionAssociations に紐付け +# (マネコンの方が安全。CLI でやる場合は get-distribution-config → JSON 編集 → update-distribution) + +# 4. キャッシュ無効化 +aws --profile $PROFILE cloudfront create-invalidation \ + --distribution-id EHFD30ZL5XZ0U --paths "/*" +``` + +## 更新 + +`url-rewrite.js` を編集したら: + +```bash +ETAG=$(aws --profile ic-gr cloudfront describe-function --name ic-gr-url-rewrite --query 'ETag' --output text) +aws --profile ic-gr cloudfront update-function \ + --name ic-gr-url-rewrite \ + --if-match "$ETAG" \ + --function-config Comment="Rewrite /foo/ to /foo/index.html for Next.js static export",Runtime=cloudfront-js-2.0 \ + --function-code fileb://url-rewrite.js + +ETAG=$(aws --profile ic-gr cloudfront describe-function --name ic-gr-url-rewrite --query 'ETag' --output text) +aws --profile ic-gr cloudfront publish-function --name ic-gr-url-rewrite --if-match "$ETAG" +``` + +publish 後、各ディストリビューションへの反映は数分かかる。CloudFront Function の +更新だけならディストリビューション側の再デプロイは不要(紐付け済みなら自動で新版が使われる)。 diff --git a/infra/cloudfront/url-rewrite.js b/infra/cloudfront/url-rewrite.js new file mode 100644 index 0000000..ae45a93 --- /dev/null +++ b/infra/cloudfront/url-rewrite.js @@ -0,0 +1,26 @@ +// CloudFront Function (viewer-request) — Next.js static export ルーティング補正 +// +// 目的: S3 (REST origin + OAC) で配信している `out/` を、サブディレクトリへの +// 直リンク / リロードでも正しく `index.html` に解決させる。 +// CloudFront の DefaultRootObject は `/` にしか効かないので、 +// `/company/` → `/company/index.html` のリライトはここで行う。 +// +// ルール: +// 1. 末尾が `/` のとき: `index.html` を末尾に付与 +// 2. 末尾が `/` でなく拡張子を含まないとき: `/index.html` を付与(末尾スラッシュ無しでも閲覧可) +// 3. 拡張子があるとき (例: /_next/static/foo.js, /favicon.ico): そのまま通す +function handler(event) { + var request = event.request; + var uri = request.uri; + + if (uri.endsWith('/')) { + request.uri = uri + 'index.html'; + } else { + var lastSegment = uri.substring(uri.lastIndexOf('/') + 1); + if (lastSegment.indexOf('.') === -1) { + request.uri = uri + '/index.html'; + } + } + + return request; +}