Next.js をS3 + CloudFront で 運用してみた結果、Amplify に乗り換えた話
はじめに
Next.js で静的サイト(SSGでビルド)を作った場合、S3 + CloudFront での配信が簡単だと思い実装してみましたが、実際に運用してみると意外な落とし穴がありました。
この記事では、私が実際に試した運用手順、得られた結果、直面した課題、そして最終的にAWS Amplify Hosting に移行することにした理由をまとめます。
S3 + CloudFront での静的サイト運用
構成は以下の通りです。
- Next.js を next build で静的ビルド(/outディレクトリにエクスポート)
- ビルド成果物を S3 バケットにアップロード
- CloudFront で S3 をオリジンに設定し、静的配信
設定内容
- CloudFront のオリジンドメイン:
[S3バケット名].s3.ap-northeast-1.amazonaws.com
- オリジンパス: 空(
/index.html
は設定しない) - Default root object:
index.html
- S3 バケットポリシーで Origin Access Control (OAC) を設定し、CloudFront からの
s3:GetObject
を許可 - Next.js の画像最適化はパスが合わないため無効化 (next.config.js
images.unoptimized = true
)
上記設定によって直面した問題
上記設定により静的ビルドのNext.jsがデプロイできたが、TOPページにアクセス後、ページ遷移したあとブラウザをリロードすると 403エラーが発生
原因
SPA ではルーティングをフロントエンド (Next.js側) で処理しているため、以下のようになってしまう。
例:
/about にアクセス → 403/404エラー
ページ内リンクによるページ遷移時は JS でルーティングしているので問題ないが、リロード時は直接そのパスを S3/CloudFront が探しに行くためエラーになります。
CloudFront の エラーページ設定 (Error Pages / Custom Error Responses) を使って、403 or 404
のときは index.html
を返すようにしました。
それによって、エラー時は必ずindex.html
が表示されるようになったが、その挙動も普通にユーザーとして違和感がある。
また、エラーが発生したURLのまま表示だけindex.html
の内容でそれもおかしいと感じました。
例:
/about → 403/404エラー → index.htmlを表示 →しかしURLは /aboutのまま
CloudFront は /about/
のリクエストから /about/index.html
を自動で解決できず、Default root object
はルート /
にしか適用されませんでした。
CloudFrontでの解決策
調べたところによると、Lambda@Edge でリクエストを書き換えれば対応可能みたいだが、運用コストとリクエスト課金が発生する
結果:静的ホスティングでは SPA用URLのリライトが難しい
それならいっそAmplifyにしてみようか!
S3 + CloudFront の運用で得た経験を踏まえ、Amplify Hosting に移行することにしました。(以前から気になってはいた)
Amplifyにしようと思った理由
理由1:ディレクトリ URL 問題の解消 ←これが一番大きな理由
- Amplify は /about にアクセスすると自動で /about/index.html を返してくれる
(Lambda@Edge を自前で書く必要がない)
理由2:運用負荷の軽減
- デプロイも GitHubと連携し自動化できる(CI/CD化の恩恵を受けられる)
理由3:コスト面
- 個人で運用する小規模サイトならほとんど料金は変わらない(若干Amplifyの方がコストは高いが現段階では誤差の範囲と判断)
まとめ
S3 + CloudFront だけでも静的配信は可能でしたが、ディレクトリURLや Next.js Image の対応には注意が必要でした。
静的サイトでURLごとに正しいページを返すには、Lambda@Edge が必要になるらしいですが、運用や開発コスト面で負担が比較的大きいと感じました。
Amplify Hosting を使えば、静的サイトでもディレクトリURLが正しく動き、運用負荷も大幅に軽減されることを実感しました。
Next.js の静的サイトを運用するなら、Amplify への移行が現実的だと感じたのでしばらくはこれで運用していこうと思っています。
Discussion