技術記事に戻る

Next.js静的サイトとCloudFront OAC構成でページリロード時に「AccessDenied」になる原因と解決策

AWSCloudFrontS3Next.js

Next.js静的サイトとCloudFront OAC構成でページリロード時に「AccessDenied」になる原因と解決策

CloudFront OAC Error Fix

Next.jsプロジェクトを静的プレレンダリング (output: 'export') して、AWS S3とCloudFront環境へデプロイする際、セキュリティベストプラクティスとして「S3の静的ウェブサイトホスティングを無効化」したまま、OAC (Origin Access Control) を用いてCloudFront経由に限定する構成が標準的です。

しかし、この構成においては一見問題なく動いているように見えても、ある致命的な落とし穴が存在します。

発生する問題の症状

トップページ (/) にアクセスし、Next.jsの内部リンク (<Link>コンポーネント) を通じて記事ページ (/tech_blog など) へ遷移する場合は、正常にページが表示されます。

しかし、記事ページでブラウザのリロード(F5)を押したり、記事ページのURLを直接ブラウザに入力してアクセスしようとすると、以下のようなエラー画面になりページを開けません。 CloudFront OAC Error Fix

<Error>
  <Code>AccessDenied</Code>
  <Message>Access Denied</Message>
</Error>

なぜこのエラーが発生するのか?

原因は、Next.jsのファイル出力形式と、CloudFront + S3(REST URL通信)のディレクトリ解決の仕様の違いにあります。

Next.jsの出力仕様 (trailingSlash: true)

Next.jsの next.config.tstrailingSlash: true を設定した場合、/tech_blog というルートはビルド時に /tech_blog/index.html というパスの実体ファイルとしてS3にエクスポートされます。

S3の挙動

CloudFront経由で「S3のRESTエンドポイント」へアクセスすると、S3は要求されたキー(URLのパス)に対して完全に一致するファイルを返そうとします。S3の「静的ウェブサイトホスティング」機能であれば、自動的に末尾のパスに /index.html を補完してくれますが、RESTエンドポイント(OAC構成時)は、この機能を持ちません。

そのため、URL https://zaregaki.net/tech_blog/ に直接アクセスした場合、S3には tech_blog/ という名前のディレクトリ実体は存在しないため「ファイルが見つからない(404 Not Found)」状態となり、S3の公開アクセス権限設定によりそれが 403 AccessDenied に変換されて表示されてしまいます。

解決策: CloudFront FunctionsでURLを書き換える

S3を非公開(OAC構成)のまま維持する場合、S3へリクエストが届く前に、CloudFront側でURLの末尾に index.html を自動付与する必要があります。そのための軽量で最適な解決策がCloudFront Functionsです。

手順

  1. CloudFront関数の作成 AWS管理コンソールの CloudFront メニューから「関数 (Functions)」を開き、「関数の作成」をクリックします。名前に「RewriteIndexHtml」等をつけます。

  2. 関数のコードを入力 以下のコードをエディタに貼り付けて保存し、「関数を発行(Publish)」します。

function handler(event) {
    var request = event.request;
    var uri = request.uri;

    // パスが '/' で終わる場合は 'index.html' を付与 (例: /tech_blog/ -> /tech_blog/index.html)
    if (uri.endsWith('/')) {
        request.uri += 'index.html';
    } 
    // パスに拡張子が含まれていない場合は '/index.html' を付与 (例: /tech_blog -> /tech_blog/index.html)
    else if (!uri.includes('.')) {
        request.uri += '/index.html';
    }

    return request;
}

CloudFront OAC Function CloudFront OAC Function Publish

  1. ディストリビューションに「ビヘイビア」として割り当て 対象のCloudFrontディストリビューションにて、「ビヘイビア (Behaviors)」タブからデフォルトのビヘイビアを編集します。下部にある「関数の関連付け (Function associations)」を開き、以下の通り設定します。
  • イベントタイプ (Event type)ビューアクリクエスト (Viewer Request)
  • 関数タイプ (Function type)CloudFront Functions
  • 関数 ARN / 名前:先ほど作成した関数名を選択

この設定を保存してCloudFrontに反映(デプロイ)されると完了です。

まとめ

Next.jsでのSSGアプリケーションとAWS(CloudFront/S3)の組み合わせは非常にスケーラブルですが、ディレクトリの振る舞いにおいて躓きやすいポイントがあります。OAC構成を採用する際は、CloudFront FunctionsによるURLリライトの設定をセットで行うことを強く推奨します。

ところで、今回は生成AIに関連する画像を生成してもらったのですが、すごいですね!

CloudFront OAC Error Fix

関連記事 その1: AWSの静的サイト配信で必ず出会う「OAI」と「OAC」の違いを、初心者向けにわかりやすく調査・解説します。なぜ新機能のOACへの移行が推奨されているのか?