LPなのにLambda?引き継いだNext.jsサイトをS3+CloudFrontに移行したらコストが9割消えた話

目次

「えっ、これLambdaで動いてるの?」と気づいた瞬間

ある日、知人から「コーポレートサイトのインフラを見てもらえない?」と相談を受けて、AWSコンソールを覗いた瞬間、私は思わず声が出ました。

えっ、このサイト、Lambdaで動いてるの?

サイト自体は完全なコーポレートサイト + LP + メディア記事のような構成で、ユーザーごとに表示が変わる要素もなければ、リアルタイム更新もない。お問い合わせフォームすらメインじゃない、ほぼ完全な静的コンテンツ。なのに、リクエストが来るたびにLambdaが起動して、Next.jsのサーバーレンダリングを毎回実行していたのです。

結論を先に言ってしまうと、このケースでは next export で静的HTML化して、S3 + CloudFront で配信するのが正解。実際にこの構成変更を提案したところ、月間のインフラコストが約9割削減でき、表示速度もコールドスタートから解放されて爆速になりました。

本記事では、似たような構成を引き継いでしまったエンジニア向けに、「なぜLambdaじゃなくてS3+CloudFrontが正解なのか」「移行の具体的な手順」「Vercelじゃダメなの?という当然の疑問への回答」まで実体験ベースで解説します。

なんでLambda構成になっちゃったのか(推測)

静的サイトがLambdaで動いている、という構成は実はそんなに珍しくありません。最近特に増えている印象があります。よくある経緯はこんな感じ。

パターン1:AIにNext.jsで作らせて、そのままAWSへ

「コーポレートサイトを作って」と Claude Code や Cursor に頼むと、当然のように Next.js でコードが生成されます。Next.jsはモダンで型安全で開発体験が抜群なので、出力としては正しい。

でも、その後の「AWSにデプロイして」という指示で、SST や Serverless Framework を使った Lambda 配信構成が組まれることがある。動くは動くけど、静的なコーポサイトに対してオーバースペックな構成です。

パターン2:Vercelに置けない事情があった

Next.jsの本来の置き場所はVercel。でも、AWS縛りや既存インフラとの統合の都合で、AWSに置かなければならないケースも多い。そこで「とりあえず動かす」優先で Lambda 構成になりがち。

パターン3:「将来動的処理が増えるかも」という保険

「いずれ管理画面とかAPIとか作るかもしれないし、SSRできるようにしておこう」という未来予測の保険。YAGNI原則(You Aren’t Gonna Need It)に違反しがちな思考パターンですね。実際に必要になったときに必要な分だけ追加すれば良いのに、最初から複雑な構成を選んでしまう。

どのパターンも、悪気があるわけではないんです。でも結果として、運用コスト・パフォーマンス・保守性のすべてで損をする構成になっていることが多い。

そもそもVercelじゃダメなの?という当然の疑問

ここで多くの読者が思うはず。「そもそもNext.jsならVercelに置けばよくない?」と。

その指摘は基本的に正しいです。Vercelは Next.jsとVercelならサーバーいらない? でも解説されている通り、Next.jsを最も簡単・最速で動かせる公式の選択肢で、SSG・SSR・ISRすべてを自動で最適化してくれます。

Vercelで全然OKなケース

  • 個人開発、スタートアップMVP
  • フロントエンドエンジニア中心のチーム
  • AWSの知識を必要とせず最速で公開したい
  • 特殊な制約がない普通のWebアプリ

Vercelを選ばない/選べない現実的な理由

とはいえ、実務ではVercelに置けない・置きたくない事情もよくあります。

  • AWS縛り:会社のセキュリティポリシーや既存インフラがAWS統一
  • 大規模時のコスト:トラフィック多めのメディアサイトでは、Vercelの帯域料金が一気に跳ね上がる
  • 既存AWS資産の活用:Route 53、CloudFront、IAM、VPC等を共通管理したい
  • エンタープライズ要件:VPC内通信、特殊な認証連携、コンプライアンス要件
  • クライアントの希望:「全部AWSで統一して」と指定されるケース

Vercel・Lambda・S3+CloudFront 3択比較

選択肢 得意なケース 苦手なケース 静的サイト適性
Vercel Next.jsの最速立ち上げ、SSR/ISR必要 AWS縛り、大規模帯域
Lambda+API Gateway 動的処理、認証付きページ、API 静的コンテンツの高頻度配信 △(オーバースペック)
S3+CloudFront 静的サイト、LP、メディア記事 動的処理、認証必須サイト

本記事は、「AWSで運用する前提」かつ「サイトが静的中心」という現場で頻発するケースに焦点を当てて、S3+CloudFront構成への移行を解説していきます。デプロイ先全般の選び方は Vercel・Render・Herokuなどを比較 も参考になります。

Next.jsのレンダリングモード再確認

適切な構成を選ぶには、まず Next.js の各レンダリングモードがインフラに何を要求するかを理解する必要があります。

モード ビルド時の出力 必要なインフラ
SSG(静的生成) HTML/CSS/JS の静的ファイル S3+CloudFront でOK LP、ブログ記事
SSR(サーバーサイド) 毎リクエスト時にサーバー実行 Lambda/EC2が必須 個人ダッシュボード
ISR(増分静的再生成) 静的ファイル + バックグラウンド更新 Vercelが最適 頻繁更新する記事サイト
CSR(クライアント) JSバンドル + 空のHTML S3+CloudFront でOK SPA、管理画面

ポイントは、SSGとCSRはサーバー実行が不要ということ。つまり、ビルド時に作られた静的ファイルを配信するだけで動くので、Lambdaは不要です。

LP/コーポレートサイトの正体は「実は静的」

「うちのサイトは動的だよ、だってブログ記事も更新するし…」と思うかもしれません。でも、よく見直すと大半のページは静的で問題ないケースが多いんです。

勘違いしやすいパターン

「動的に見えるけど、実は静的でOK」な代表例

❌ 勘違い:「記事はDBから読み込むからSSR必要」
✅ 実際:ビルド時にDB読んで静的HTML化すればOK(SSG)

❌ 勘違い:「お問い合わせフォームがあるから動的」
✅ 実際:本文は静的、送信処理だけAPI Gateway+Lambda

❌ 勘違い:「ヘッドレスCMSと連携するから動的」
✅ 実際:CMSの更新時にビルドを走らせれば静的でOK

❌ 勘違い:「画像最適化があるから next/image でSSR必要」
✅ 実際:ビルド時最適化 or CloudFrontの画像処理で代用可能

つまり、「ユーザーごとに表示が変わる」「リアルタイム性が必要」のどちらかが本当に必要なページ以外は、静的化できるということ。コーポレートサイトやLPは99%静的で十分です。

設計ミスマッチの実害:Lambda構成の3つの問題

「動いてるからいいじゃん」と思うかもしれません。でも、静的サイトをLambdaで動かしている構成には、明確な実害があります。

問題1:コストが無駄に高い

月間10万PVのサイトで、ざっくり試算してみます(東京リージョン目安)。

項目 Lambda構成 S3+CloudFront構成
リクエスト処理 Lambda実行料金(10万回) S3 GET料金(10万回)
データ転送 Lambda→CloudFront転送 S3→CloudFront転送(無料)
監視・ログ CloudWatch Logs常時発生 必要最小限
月額目安 $30〜$80程度 $3〜$8程度

あくまでざっくり試算ですが、同じ表示内容でコストに10倍の差が出ることはザラです。AWS Pricing Calculator で実際のトラフィックを入れて試算すると、自分のケースでの差額がはっきり見えるはず。

問題2:コールドスタートで初回表示が遅い

Lambdaは一定時間アクセスがないと、コンテナがシャットダウンされます。次のリクエスト時に新しくコンテナを起動するため、初回リクエストで500ms〜2秒程度の遅延が発生します。

S3+CloudFrontなら、CloudFrontエッジに静的ファイルがキャッシュされているので、常に数十msで応答。コーポレートサイトの第一印象として、表示速度の差は致命的です。

問題3:突発トラフィックに弱い

SNSでバズったり、メディア掲載で一時的にアクセスが集中した場合、Lambdaは同時実行数の制限に引っかかってスロットリングが発生することも。S3+CloudFrontなら、キャッシュされた静的ファイル配信は実質無制限に近いスケーラビリティがあります。

正しい構成:next export + S3 + CloudFront 移行手順

ここから具体的な移行手順です。Next.js 14系以降を前提に、現代的なやり方で解説します。

Step 1:next.config.js で静的エクスポート設定

Next.js 13.3 以降、静的エクスポートは output: 'export' 設定で有効化します。

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'export', // 静的エクスポート有効化

  // 画像最適化を無効化(静的エクスポート時は使えない)
  images: {
    unoptimized: true,
  },

  // 末尾スラッシュ統一(CloudFrontと相性◎)
  trailingSlash: true,
}

module.exports = nextConfig

Step 2:ビルドして静的ファイルを生成

# ビルド実行
npm run build

# out/ ディレクトリに静的ファイルが生成される
ls out/
# index.html
# about/
# blog/
# _next/
# ...

Next.js 14系では next build 一発で out/ ディレクトリに完成形が出力されます。next export コマンドは Next.js 14 で廃止され、output: 'export' 設定に統合されました。

Step 3:S3バケットを作成

# S3バケット作成
aws s3 mb s3://example-corporate-site --region ap-northeast-1

# パブリックアクセスをブロック(CloudFrontからのみアクセスする想定)
aws s3api put-public-access-block \
  --bucket example-corporate-site \
  --public-access-block-configuration "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"

AWS CLIの基本的な使い方は AWS CLIで開発が爆速になる も参考にどうぞ。

Step 4:S3にファイルをアップロード

# out/ の中身を一括アップロード
aws s3 sync ./out s3://example-corporate-site \
  --delete \
  --cache-control "public, max-age=31536000, immutable" \
  --exclude "*.html"

# HTMLファイルはキャッシュ短めに
aws s3 sync ./out s3://example-corporate-site \
  --delete \
  --cache-control "public, max-age=0, must-revalidate" \
  --exclude "*" \
  --include "*.html"

キャッシュ戦略のポイントは、静的アセット(JS/CSS/画像)は超長期キャッシュ、HTMLは即時更新可能に分けること。Next.jsは静的アセットにハッシュ付きファイル名を生成するので、長期キャッシュしても安全です。

Step 5:CloudFrontディストリビューション作成

CloudFrontを前段に置いて、S3を直接公開せずに配信します。重要な設定ポイントは以下。

  • Origin:S3バケット(OAC = Origin Access Control を使用)
  • Default Root Objectindex.html
  • Custom Error Pages:404を 404.html または SPA向けに index.html へリダイレクト
  • Viewer Protocol Policy:HTTPS Redirect
  • Compress objects automatically:有効化(gzip/brotli自動圧縮)

Step 6:Route 53でドメイン紐付け

# ACMでSSL証明書を取得(us-east-1リージョンで必須)
aws acm request-certificate \
  --domain-name example.com \
  --subject-alternative-names www.example.com \
  --validation-method DNS \
  --region us-east-1

# CloudFrontディストリビューションに証明書を紐付け、
# Route 53のAレコードでCloudFrontへエイリアス設定

ACM証明書は 必ず us-east-1(バージニア北部) で発行する必要があります。CloudFrontがグローバルサービスのため、東京リージョンの証明書では使えません。ハマりやすいポイントなので注意。

部分的に動的処理が必要な場合の落とし所

「お問い合わせフォームの送信処理だけは動的にしたい」「会員ページだけ認証が必要」というケースは多いはず。完全静的じゃなくても、動的処理が必要な部分だけ Lambda を使うハイブリッド構成が現実的です。

構成例:静的サイト + 部分API

[ユーザー]
   ↓
[CloudFront]
   ├─ /        → S3(静的HTML)        ← LP・記事ページ
   ├─ /about   → S3(静的HTML)        ← 会社概要
   ├─ /blog/*  → S3(静的HTML)        ← ブログ記事
   │
   └─ /api/*   → API Gateway + Lambda  ← フォーム送信・認証など

CloudFrontのBehavior(パスベースのルーティング)機能を使うと、こうした使い分けが可能です。/api/* だけ Lambda に向け、それ以外は S3 から配信。これで「コーポレートサイトは爆速、フォームだけ動的」という理想構成が完成します。

Lambda側の実装は AWS Lambda + API Gatewayで始めるサーバーレスAPI開発 の手順をそのまま流用できます。

移行時の注意点

既存サイトをLambda構成からS3+CloudFrontに移行する際は、いくつか押さえるべきポイントがあります。

既存URLの維持(SEO対策)

移行で一番怖いのがURL変更によるSEO評価リセット。基本的には既存URLをそのまま再現する形で移行するのが鉄則です。

  • 末尾スラッシュ有無(/about vs /about/)の統一
  • 大文字小文字の扱い
  • クエリパラメータの動作確認
  • 301リダイレクト設定が必要な場合の対応

CI/CDの組み直し

Lambdaデプロイ用のCI/CD(Serverless Framework、SST等)から、S3デプロイ用のCI/CDに切り替えます。GitHub Actionsで書くと、こんなにシンプルに。

# .github/workflows/deploy.yml
name: Deploy to S3+CloudFront

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install & Build
        run: |
          npm ci
          npm run build

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
          aws-region: ap-northeast-1

      - name: Deploy to S3
        run: |
          aws s3 sync ./out s3://example-corporate-site --delete

      - name: Invalidate CloudFront
        run: |
          aws cloudfront create-invalidation \
            --distribution-id ${{ secrets.CF_DISTRIBUTION_ID }} \
            --paths "/*"

CloudFrontキャッシュの無効化(Invalidation)を忘れずに

S3にアップロードしただけでは、CloudFrontのキャッシュが古いまま。create-invalidation を必ず実行してキャッシュをクリアします。Invalidation は月1,000パスまで無料なので、コストも気にする必要なし。

それでもLambdaが必要な「本当の」ケース

誤解のないように補足しておくと、Lambdaは決して悪者じゃありません。本当にLambdaが必要なケースもしっかり存在します。

  • 認証付きページ:ユーザーごとに表示が違う、ログイン必須のダッシュボード
  • リアルタイム性が必要:在庫、株価、刻々と変わる情報を表示
  • パーソナライズ:閲覧履歴に応じて出すコンテンツを変える
  • ABテスト:ユーザーごとに別バージョンのページを返す
  • 動的なメタタグ生成:OGP画像をリクエストごとに動的生成するなど

こうした要件があるページは、迷わずLambdaやサーバーレス構成を使うべきです。問題なのは、動的処理が必要ないのに「念のため」「とりあえず」でLambdaを使ってしまうこと

まとめ:シンプルなものはシンプルに

本記事のポイントを整理しておきます。

  • 静的中心のサイト(LP、コーポサイト、記事)を Lambda で動かすのは過剰設計
  • AWS縛りでなければ Vercel が Next.js の最適解
  • AWSで運用するなら next export + S3 + CloudFront が王道
  • 動的処理が必要な部分だけ API Gateway + Lambda をハイブリッドで配置
  • 移行時はURL維持・キャッシュ戦略・CI/CDを要チェック
  • コスト・パフォーマンス・スケーラビリティの3点で圧倒的に有利

「サーバーレスだから」「Next.jsだから」という思考停止で構成を決めると、無駄なコストとパフォーマンス低下を招きます。そのページに本当に動的処理が必要か?を毎回問い直す習慣をつけると、シンプルで強いインフラが組めるようになります。

もし今、引き継いだNext.jsサイトのインフラに違和感を感じているなら、まずは AWS Pricing Calculator で現状コストと S3+CloudFront 構成のコストを比較してみてください。意外な金額差に驚くはずです。シンプルなものはシンプルに、を意識して、サイトを爆速・爆安にしていきましょう。

Next.js × AWS インフラをさらに磨く関連記事

本記事の構成を実践したら、Next.jsのデプロイ戦略やAWS運用の引き出しをさらに増やしていきましょう。インフラ選択の判断軸が広がる関連記事を厳選しました。

Next.js・デプロイ戦略を深める

AWS運用・サーバーレスを強化

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次