この記事では、CloudFront を介して S3 にホストされた Next.js アプリケーションを配信するための設定と、GitHub Actions を使ったデプロイ自動化について必要になる設定のメモです。
Next.js アプリケーションを S3 でホストする方法を検索して見てみると、S3 のパブリックアクセスを許可して直接参照する構成を見かけることがあります。しかし、この方法ではキャッシュの問題やセキュリティリスク、通信量増加といった課題を発生させる可能性があります。
そこで、本記事では以下のメリットを享受できる構成を行った時の設定のメモとなります。
利用したアプリケーションを構築中なので、完全な設定については別途出そうと思います。
・セキュリティ強化: S3 への直接アクセスを制限し、CloudFront 経由のみに限定します。
・パフォーマンス向上: CloudFront のキャッシュ機能を最大限に活用し、表示速度を向上させます。
・運用効率化: GitHub Actions を利用してデプロイプロセスを自動化します。
1. アーキテクチャ概要
本プロジェクトでは、以下の3層構成を採用し、Next.js アプリケーションを効率的に配信します。
- フロントエンド: Next.js (静的エクスポート)
- CDN: CloudFront Distribution
- ストレージ: S3 バケット
2. S3バケット構成
2.1 バケット設計
2つのS3バケットを使用
ここでは、以下の2つの S3 バケットを使用する前提で説明を進めます。バケット名は適宜ご自身の環境に合わせて変更してください。
- app: Next.js アプリケーション本体のファイルを格納します。
- contents-files: アプリケーションとは直接関係のない静的コンテンツファイルを格納します。
2.2 Origin Access Control (OAC)設定
S3 へのアクセスを CloudFront からのみに制限するため、Origin Access Control (OAC) を使用します。よりセキュアに S3 と CloudFront を連携できます。
AWS コンソールでの手動設定は不要で、以下のように template.yml で定義することで自動的に作成されます。
・template.yml
S3OAC:
Type: AWS::CloudFront::OriginAccessControl
Properties:
OriginAccessControlConfig:
Name: !Sub 'OAC-${AWS::StackName}'
OriginAccessControlOriginType: s3
SigningBehavior: always
SigningProtocol: sigv4
2.3 S3の設定
S3 バケットは事前に作成しておきます。重要な点として、パブリックアクセスを完全にブロックした上で、特定の CloudFront ディストリビューションからのみアクセスを許可する設定を行います。
「ブロックパブリックアクセス」の設定: S3 バケットの「アクセス許可」タブから、「パブリックアクセスをすべてブロック」を有効にします。
バケットポリシーの設定: CloudFront からのアクセスのみを許可するバケットポリシーを設定します。
その上で特定のCloudFrontだけアクセスを許可します。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::app/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::(ユーザーID):distribution/(CloudFrontのID)"
}
}
}
]
}
このポリシーでは、arn:aws:s3:::app/* (app バケット内のすべてのオブジェクト)に対して、特定の CloudFront ディストリビューションからの s3:GetObject アクションのみを許可しています。
CloudFront ディストリビューションの設定画面で、オリジン設定時に表示されるポリシーをコピーして利用すると、より正確な AWS:SourceArn の値を取得できます。
注意点: CloudFront の設定バージョンや OAC の有効/無効によって、ポリシーの内容が若干異なる場合があります。設定に成功した際の表示をよく確認してください。
具体的な設定はCloudFrontのオリジンの設定の中に以下の画像の部分があり、ポリシーをコピーで取得でき参考にできます。
少し設定に成功した時のバージョンやIDの項目の有無などが異なるのでご注意ください。
3. CloudFront Distribution設定
samコマンドで構築します。
3.1 オリジン設定
Next.js アプリケーション本体と静的コンテンツファイルをそれぞれ異なる S3 バケットに配置するため、CloudFront にも2つのオリジンを設定します。これにより、それぞれのコンテンツに異なるキャッシュ設定を適用できるようになります。
・template.yml
Origins:
# コンテンツ用S3オリジン
- Id: ContentsS3Origin
DomainName: !Sub 'contents-files.s3.amazonaws.com'
OriginAccessControlId: !GetAtt OAC.Id
S3OriginConfig:
OriginAccessIdentity: ''
# アプリケーション用S3オリジン
- Id: AppS3Origin
DomainName: !Sub 'app.s3.amazonaws.com'
OriginAccessControlId: !GetAtt OAC.Id
S3OriginConfig:
OriginAccessIdentity: ''
3.2 キャッシュビヘイビア設定
コンテンツの種類に応じてキャッシュ時間を最適化するため、キャッシュビヘイビアを2つに分割します。
静的コンテンツ用 (/contents/*):
-
TargetOriginId: ContentsS3Origin
-
PathPattern: /contents/* (例: https://your-domain.com/contents/image.png のようなパスにマッチ)
-
ViewerProtocolPolicy: redirect-to-https
-
AllowedMethods: [GET, HEAD, OPTIONS]
-
ForwardedValues (クエリ文字列、Cookie): false / none (キャッシュヒット率向上のため)
-
DefaultTTL: 86400 秒 (24時間)
-
MaxTTL: 31536000 秒 (1年間)
-
Compress: true (圧縮を有効化)
Next.js アプリ用 (/app/*):
-
TargetOriginId: AppS3Origin
-
PathPattern: /app/* (例: https://your-domain.com/app/index.html のようなパスにマッチ)
-
ViewerProtocolPolicy: redirect-to-https
-
AllowedMethods: [GET, HEAD, OPTIONS]
-
ForwardedValues (クエリ文字列、Cookie): false / none
-
DefaultTTL: 86400 秒 (24時間)
-
MaxTTL: 31536000 秒 (1年間)
-
Compress: true
・template.yml
静的コンテンツ用(/contents/*)
- TargetOriginId: ContentsS3Origin
PathPattern: "/contents/*"
ViewerProtocolPolicy: redirect-to-https
AllowedMethods: [GET, HEAD, OPTIONS]
ForwardedValues:
QueryString: false
Cookies:
Forward: none
DefaultTTL: 86400
MaxTTL: 31536000
Compress: true
Next.jsアプリ用(/app/*)
- TargetOriginId: AppS3Origin
PathPattern: "/app/*"
ViewerProtocolPolicy: redirect-to-https
AllowedMethods: [GET, HEAD, OPTIONS]
ForwardedValues:
QueryString: false
Cookies:
Forward: none
DefaultTTL: 86400
MaxTTL: 31536000
Compress: true
3.3 構築
sam buildとsam deployで実行
- name: Build SAM Application
run: sam build -t ./backend/src/lambda/template.yaml
- name: Deploy SAM Application
run: sam deploy --region ap-northeast-1 --stack-name aimon --no-confirm-changeset --no-fail-on-empty-changeset --config-file ./backend/src/lambda/samconfig.toml
4. Next.js設定
4.1 静的エクスポート設定
Next.js アプリケーションを静的サイトとしてビルドするために、next.config.ts に以下の設定を追加します。
// next.config.ts
const nextConfig: NextConfig = {
output: 'export', // 静的エクスポートを有効
images: {
unoptimized: true, // 画像最適化を無効(S3用)
},
basePath: process.env.NEXT_PUBLIC_BASE_PATH || '',
assetPrefix: process.env.NEXT_PUBLIC_BASE_PATH ? `${process.env.NEXT_PUBLIC_BASE_PATH}/` : '',
};
- output: 'export' により、Next.js アプリケーションが静的な HTML, CSS, JavaScript ファイルとして出力されます
- images: { unoptimized: true } は、Next.js の画像最適化機能を無効にします。これは、S3 に静的ファイルを配置する場合に必要となることが多い設定です
- basePath と assetPrefix は、CloudFront でのパス設定に合わせて、必要に応じて環境変数で調整できるようにしています
5. CI/CDパイプライン
GitHub Actions を利用して、フロントエンドと静的コンテンツのデプロイを自動化します。
5.1 フロントエンドデプロイ(deploy-frontend.yml)
Next.js アプリケーションのビルド、S3 への同期、CloudFront キャッシュの無効化を行います。
Next.jsビルド
・GithubAction用の設定ファイル
- name: Build Next.js project
run: npm run build
working-directory: ./frontend
S3同期
・GithubAction用の設定ファイル
- name: Sync frontend-s3 directory to S3 bucket
run: aws s3 sync ./frontend/out s3://app/ --delete
- ./frontend/out は Next.js の静的エクスポートで生成されるファイルが出力されるディレクトリです
- s3://app/ は Next.js アプリケーションを格納する S3 バケットです。
--delete オプションにより、S3 バケットに存在しないローカルのファイルが削除されます。
CloudFrontキャッシュ無効化
・GithubAction用の設定ファイル
- name: Invalidate CloudFront cache
run: aws cloudfront create-invalidation --distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} --paths "/*"
- デプロイ後、CloudFront のキャッシュを無効化することで、最新のコンテンツがユーザーに配信されるようにします
--paths "/*" とすることで、オリジンが複数あっても全てのパスのキャッシュを無効化します。キャッシュ無効化は回数制限があり料金を発生させる可能性があるため、慎重に設定してください。
5.2 静的コンテンツデプロイ(deploy-s3.yml)
静的ファイル同期
・GithubAction用の設定ファイル
- name: Sync frontend-s3 directory to S3 bucket
run: aws s3 sync ./frontend-s3/ s3://contents-files --delete
- ./frontend-s3/ は静的コンテンツファイルが格納されているローカルディレクトリです
- s3://contents-files は静的コンテンツファイルを格納する S3 バケットです
6. 一般的な設定のポイント
6.1 セキュリティ
-
圧縮有効: CloudFront で Gzip 圧縮を有効にし、静的ファイルの転送サイズを削減します
-
適切な TTL: コンテンツタイプ(アプリケーションファイル、静的画像など)に応じて、CloudFront のキャッシュ期間 (TTL) を適切に設定し、キャッシュヒット率とコンテンツの鮮度のバランスを取ります
-
HTTP/2 対応: CloudFront で HTTP/2 を有効にし、より効率的な通信を実現します
6.2 パフォーマンス
圧縮有効: 静的ファイルの圧縮を有効化
適切なTTL: コンテンツタイプに応じたキャッシュ期間設定
HTTP/2対応: CloudFrontでHTTP/2を有効化
6.3 運用
-
自動デプロイ: main ブランチへのプッシュをトリガーに自動デプロイを実行し、デプロイの手間を省きます
-
キャッシュ管理: デプロイ時に CloudFront キャッシュの自動無効化を行い、常に最新のコンテンツが配信されるようにします
7. 実装時の注意点
-
パス設計: アプリケーションのルーティング、API エンドポイント、静的コンテンツのパスを明確に分離して設計することで、CloudFront のキャッシュビヘイビアを効果的に設定できます
-
CORS 設定: API Gateway と CloudFront を連携する場合など、必要に応じて CORS (Cross-Origin Resource Sharing) 設定を適切に行う必要があります
-
エラーハンドリング: SPA (Single Page Application) のルーティングで 403 エラーが発生するような場合は、CloudFront のカスタムエラーページ設定で 403 エラーを 200 OK にリダイレクトするなど、適切なエラーハンドリングを設定してください
-
バケットポリシー: OAC を使用する場合、S3 バケットポリシーは CloudFront の OAC からのアクセスのみを許可するように正しく設定されているかを確認してください
この構成により、Next.js アプリケーションをセキュアかつ効率的に CloudFront + S3 で配信し、運用負荷を軽減しながら高いパフォーマンスを実現できます。