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 エラーを解決しましょう!