Next.js の静的サイトを Amazon S3 にデプロイし、CloudFront を介して配信する際、ルートページ以外のパスで 403 Forbidden エラーが発生することがあ離ました...
この問題が発生する理由と、CloudFront Functions を使用した解決策を調査してみました.
何が起きている?: 403 Forbidden エラー
デフォルトでは、ユーザーが https://example.com/about/ のような URL にアクセスすると、CloudFront は S3 バケットから about/ オブジェクトをリクエストします。しかし、S3 はオブジェクトストレージサービスであるため、デフォルトでは about/ を index.html ファイルを配信するディレクトリとして扱いません。

Next.js は、静的エクスポート (next build && next export) でビルドされると、各ルートの HTML ファイルを生成します。例えば、pages/about.js は out/about.html としてコンパイルされます。ユーザーが /about/ をリクエストしても、CloudFront はこれを自動的に /about.html や /about/index.html (Next.js で trailingSlash を設定している場合) に書き換えません。その結果、CloudFront は存在しないオブジェクトを S3 に要求し、403 Forbidden エラーが発生します。
ルートパス (/) は、通常 CloudFront のデフォルトルートオブジェクト (多くの場合 index.html) が正しく提供されるため、問題なく動作します。
解決策: URI 書き換えのための CloudFront Functions
CloudFront Functions は、エッジでリクエストとレスポンスを操作するためのJavaScriptベースで動作する関数を提供するサービスです。CloudFront Functions を使用して、着信リクエストをインターセプトし、S3 内の正しい HTML ファイルを指すように URI を書き換えることができます。
この問題を解決するための CloudFront Function コードは以下の通りです。
function handler(event) {
var request = event.request;
var uri = request.uri;
// 1. URIがファイル拡張子を含まない場合
// 例: /about -> /about.html
// ただし、ルートパス / は除く
if (uri !== '/' && !uri.includes('.')) {
request.uri = uri + '.html';
}
// 2. URIが "/" で終わる場合、"index.html" を追加
// 例: /about/ -> /about/index.html
if (uri.endsWith('/')) {
request.uri += 'index.html';
}
return request;
}
最適化された CloudFront Function は、より汎用的なロジックで Next.js 静的エクスポートのルーティング問題を解決します。
-
URI がファイル拡張子を含まない場合の
.html追加:if (uri !== '/' && !uri.includes('.')) { request.uri = uri + '.html'; }- これは、リクエストされた URI がルートパス (
/) ではなく、かつファイル拡張子 (.html,.css,.jsなど) を含まない場合に適用される。 - たとえば、
/emotionへのリクエストは/emotion.htmlに書き換えられます。 - これにより、個々のページパスをハードコードすることなく、Next.js が生成するすべてのページに対応できます。
-
URI が末尾のスラッシュで終わる場合の
index.html追加:if (uri.endsWith('/')) { request.uri += 'index.html'; }- これは、リクエストされた URI が末尾のスラッシュで終わる場合に適用される (例:
/about/) - URI に
index.htmlを追加し、/about/を/about/index.htmlに変換します。これは、Next.js が静的エクスポートでディレクトリ内にindex.htmlを生成する構成に対応する - この条件は、前述の
.html追加ルールが適用された後でも有効。例えば、/about/が/about.htmlに書き換えられず、そのまま/about.htmlになることを保証する。
つまり...
-
/pathのようなクリーンな URL を/path.htmlに変換する -
/path/のような末尾スラッシュ付きの URL を/path.htmlに変換する -
/(ルート) はindex.htmlのままです (CloudFront のデフォルトルートオブジェクト設定によって処理されるか、または2番目の条件で/index.htmlになります) - 画像やCSS、JavaScriptファイルなどの静的アセット (
.png,.css,.jsなど) は、URI に拡張子が含まれるため、この関数によって変更されません。
これにより、CloudFront Functions の設定を簡素化し、Next.js アプリケーションに新しいページを追加するたびに更新する必要がなくなります!
CloudFront Functions のデプロイ方法:
1. 関数の作成:
- AWS の CloudFront コンソールに移動します。
- 「関数」のページで、新しい関数を作成します。
2. 関数の公開:
3. CloudFront ディストリビューションとの関連付け:
-
CloudFront ディストリビューションの設定に移動します。
-
「関数関連付け」の下で、「ビューワーリクエスト」を選択します。
この CloudFront Function を実装することで、S3 および CloudFront にデプロイされた Next.js アプリケーションはすべてのルートを正しく提供するので、403 Forbidden エラーを解決しましょう!








