AWS で Next.js ISR を簡単に動かすやり方|SST v3 で確実にデプロイできる構築方法

2025.12.03

Next.js の ISR(Incremental Static Regeneration)は、静的生成の高速性と柔軟なコンテンツ更新を両立できる仕組みです。しかし、これを AWS 上で安定して運用しようとすると、CloudFront・S3・Lambda など複数のサービスを組み合わせる必要があり、設定が複雑になりがちです。

本記事では、Next.js で ISR を設定したページを作成し、それを SST v3 を使って AWS 上にデプロイして動作を確認するまでの流れをまとめています。

Next.js ISR を AWS で扱う意義

ISR は静的生成のメリットを活かしながら、バックグラウンドでページを更新できる仕組みです。AWS と組み合わせることで、CloudFront による高速配信や Lambda による動的再生成など、パフォーマンスと可用性の面でメリットが広がります。

ISR を AWS に載せる意味は、単に「Next.js をホスティングできる」というだけではなく、次のような運用性を得られる点にあります。

  • ページ数が多くても配信速度が安定しやすい

  • 更新頻度の異なるページ群を手軽に扱える

  • 静的配信の強さを活かしつつ、CMS ベースの更新にも対応できる

  • 長期運用でパフォーマンスを落としにくい

ISR は大規模なサイト構成や、更新頻度のばらつきが大きいコンテンツに向いています。AWS と組み合わせることで、その特性を最大限に活かしやすくなります。

ISRのSEO・パフォーマンス観点での重要性

ISR は静的配信の高速性を維持しつつ、ページ内容を柔軟に更新できる点で、SEO とパフォーマンスの改善に寄与します。CloudFront と組み合わせることで表示速度が安定し、ユーザー体験の向上にもつながります。

ページ数が多く、更新頻度が一定ではない構成を扱う場合、完全な SSG や SSR よりも ISR が適していることがあります。AWS 上で構成することで、長期運用でも安定した応答性能を維持しやすくなるため、ISR を採用する理由は技術面だけでなく運用面にも広がります。

本記事の概要

この記事では、Next.js で作成した ISR ページを SST v3 を利用して AWS 上にデプロイし、実際に ISR が期待どおりの周期で再生成されていることを CloudFront 越しに確認できる状態までを扱います。

進める内容は大きく次の流れです。

  • Next.js の環境を用意し、ISR ページを作成する

  • SST v3 を設定し、Next.js を AWS に最適な形でデプロイする

  • AWS 上の複数サービスを個別に確認しなくても済むよう、SST Console を利用して全体の状態を把握する

  • CloudFront を経由したアクセスで ISR の更新が正しく反映されるか確認する

ISR・AWS・SST がどのように関係しながら動作しているかを確認してみましょう。

Next.js の環境をセットアップする

まずは ISR の動作を試せる Next.js プロジェクトを準備します。標準構成のままで問題ありません。

npx create-next-app@latest next-isr-demo --ts
cd next-isr-demo

TypeScript と App Router を使う前提で進めるため、それぞれ Yes を選択します。Tailwind などの追加項目はプロジェクトの方針に合わせて選択すれば十分です。

作成後は開発サーバーが起動するか確認します。

npm run dev

初期画面が表示されれば、Next.js 側の環境は整っています。ISR の設定は後ほど追加するため、この段階で新しいページの構造が問題なく扱えることだけ確認しておきます。

ISR を設定したページを作成する

ISR の更新タイミングを確認しやすいよう、最初にシンプルなページを作成します。今回は現在時刻を表示し、再生成が行われた瞬間に変化がわかるようにします。

app/isr/page.tsx を作成します。

export const revalidate = 60;

export default function Page() {
  const time = new Date().toISOString();

  return (
    <main style={{ padding: 40 }}>
      <h1>ISR Demo</h1>
      <p>生成時刻: {time}</p>
    </main>
  );
}

ISR の動作は次のようになります。

  • 初回アクセスで HTML を生成する

  • 生成された HTML が 60 秒間キャッシュされる

  • 60 秒を経過したあとのアクセスで再生成が実行される

  • 新しい内容が反映されるのは、再生成が完了した後

ローカル環境でも確認できますが、AWS 上では CloudFront のキャッシュが関わるため、その違いも後ほど確認します。

SST v3 をセットアップする

ISR ページを AWS にデプロイするため、SST v3 をプロジェクトに導入します。

SSTはAWSの各リソースを自動で作成するIaC(Infrastructure as Code)というものになります。 現在稼働しているAWS上にデプロイする場合には、リソース名などに注意が必要だったりすると思いますので、別途まっさらな状態で始めると作成されるリソースの理解も進むと思います。

しかし、SSTのコマンドで作成したリソースを削除することも可能ですし、そのコマンドもインストール時に作成されますので、安心して利用できます。

AWS CLI の準備

SST は AWS CLI を利用してデプロイ処理を実行するため、事前に次の設定が必要です。

  • AWS CLI のインストール

  • aws configure による設定

  • IAM ユーザーに必要な権限が付与されていること

設定がまだの場合は、次を実行します。

aws configure

これにより SST が AWS にアクセスできる状態になります。


SST の初期化

Next.js プロジェクト直下で SST を初期化します。

npx sst@latest init

設定が完了すると sst.config.ts が生成されます。続けてローカル開発環境を起動し、SST Console を開きます。

npx sst dev

SST Console では、後のデプロイ状態の可視化に役立つ情報がまとまって表示されます。


sst.config.ts を設定する

SST v3 では sst.aws.Nextjs を使うことで、Next.js の構成を AWS に合わせて自動的に展開できます。

sst.config.ts を以下のように編集します。

import * as sst from "sst";

export default {
  config() {
    return {
      name: "next-isr-demo",
      region: "ap-northeast-1",
    };
  },
  stacks(app) {
    app.stack(function Site({ stack }) {
      const site = new sst.aws.Nextjs("MyWeb", {
        environment: {
          NEXT_PUBLIC_STAGE: stack.stage,
        },
      });

      stack.addOutputs({
        SiteUrl: site.url,
      });
    });
  },
};

この設定により CloudFront・S3・Lambda など必要なリソースが自動的に展開され、Next.js アプリの構成が AWS 上に最適化されます。


AWSにデプロイする

設定が整ったら、デプロイを実行します。

npx sst deploy --stage production

デプロイ後、CloudFront の URL が出力されます。この URL が公開された ISR ページへのアクセス先になります。

実際のサーバー上で ISR の動作を確認する

デプロイしたページにアクセスし、ISR の挙動を確認します。ローカルとは異なり、CloudFront を経由するためキャッシュの影響も含めて動作を見る必要があります。

最初のアクセスでは、デプロイ直後に生成された HTML が返されます。60 秒以内であれば同じ内容が続きますが、60 秒を超えたあとのアクセスではバックグラウンドで再生成が行われ、反映後に内容が更新されます。

ISR が意図したとおりに動作しているかを確認する際は、内容の変化だけでなく、どのタイミングで再生成が行われたのかも重要です。その点は後述する SST Console や Lambda のログで確認できるため、動作確認と合わせて利用します。

SST Console を使ってAWSのリソースを確認する

ISR の動作や AWS の構成が正しいかを確認する際、CloudFront・S3・Lambda を AWS マネジメントコンソールで個別に確認するのは手間がかかります。また、CLI コマンドだけでは分かりづらい場面もあります。

こうした状況を整理するために役立つのが SST Console です。

SST Console リソースの画面SST Console ログの画面デプロイされたリソースが一覧でき、Next.js の再生成処理に関わる Lambda のログなどもまとめて確認できます。AWS 上の各サービスを横断的に確認する必要がある ISR の構成では、特に重宝する確認手段になります。

こちらを元に、実際にAWSのマネジメントコンソールでも確認してみてください。

CloudFront のキャッシュと公開直後の反映について理解する

ISR を AWS 上で利用する場合、CloudFront と Next.js(OpenNext)がそれぞれ独立してキャッシュを扱います。

ISR の仕組みは、バックグラウンドで再生成した HTML を S3 に保存し、後続のアクセスで新しい内容が返される流れですが、CloudFront は CDN として別レイヤーでキャッシュを保持しており、ここで更新のタイミングにずれが生じることがあります。

SST v3 でのキャッシュ更新と運用の考え方

SST v3(OpenNext)は ISR の再生成処理を自動化しており、ページ内容の更新は裏側で適切に行われます。日常的な更新であれば、ISR の revalidate と CloudFront の TTL の組み合わせによって自然に反映されるため、無効化を必ずしも実行する必要はありません。

ただし、次のようなケースでは Invalidation を行うことで反映を早められます。

  • デプロイ直後に内容を即時更新したい場合

  • 特定のページだけ更新を確実に反映したい場合

  • キャッシュの状態をリセットして動作確認を行いたい場合

SST 自体は CloudFront の Invalidation を自動化しませんが、必要であれば CLI や CI/CD のワークフローに組み込むことも可能です。運用上、どのタイミングで即時反映が必要になるのかを整理し、その場面でだけ無効化する形が扱いやすい構成になります。

キャッシュクリアを行う場合の選択肢

CloudFront 側の TTL を短くする

nonVersionedFilesCacheHeaderpublic,max-age=0,s-maxage=60 のように短めに変更することで CloudFront が 60秒に一度はオリジンに取りに行くようになります。

これによりCloudFrontでキャッシュされ続けるという様な事象は起きなくなります。

設定例(sst.config.ts)

new sst.aws.Nextjs("Web", {
  assets: {
    nonVersionedFilesCacheHeader: "public,max-age=0,s-maxage=60,stale-while-revalidate=60",
  },
});

CMSのWebhookでISRとCloudFrontのキャッシュをクリア

記事コンテンツを CMS で管理している場合、「記事を公開・更新したタイミングで、Next.js の ISR キャッシュと CloudFront のキャッシュをまとめて更新したい」という要件がよくあります。このケースでは、CMSのWebhookで行う方が良いです。

  • Next.js 側に「再検証用のエンドポイント」を用意する

  • そのエンドポイントの中で

    • revalidatePath / revalidateTag などを使って ISR キャッシュを更新する

    • CloudFront の CreateInvalidation を呼び出して対象パスのキャッシュを無効化する

設定例(app/api/revalidate/route.ts)

import { NextRequest, NextResponse } from 'next/server';
import { revalidatePath, revalidateTag } from 'next/cache';
import {
  CloudFrontClient,
  CreateInvalidationCommand,
} from '@aws-sdk/client-cloudfront';

// CloudFront クライアントはモジュールスコープで1回だけ生成
const cloudFront = new CloudFrontClient({});

type CmsWebhookPayload = {
  secret: string;
  slug?: string;        // 例: "my-article"
  tags?: string[];      // 例: ["article:123"]
};

async function invalidateCloudFrontPaths(paths: string[]) {
  const distributionId = process.env.CLOUDFRONT_DISTRIBUTION_ID;
  if (!distributionId) {
    console.warn(
      '[revalidate] CLOUDFRONT_DISTRIBUTION_ID が設定されていないため、CloudFront Invalidation はスキップしました。',
    );
    return;
  }

  if (paths.length === 0) {
    return;
  }

  // CloudFront CreateInvalidation API 呼び出し
  const command = new CreateInvalidationCommand({
    DistributionId: distributionId,
    InvalidationBatch: {
      CallerReference: `${Date.now()}`, // 一意であればOK。タイムスタンプで十分です。
      Paths: {
        Quantity: paths.length,
        Items: paths,
      },
    },
  });

  await cloudFront.send(command);
}

export async function POST(req: NextRequest) {
  const body = (await req.json()) as CmsWebhookPayload;

  // 簡易な認証(CMS 側の Webhook 設定で同じ secret を送る)
  if (body.secret !== process.env.REVALIDATE_SECRET) {
    return NextResponse.json({ message: 'Invalid secret' }, { status: 401 });
  }

  const pathsToRevalidate: string[] = [];

  // 例: /articles/[slug] を ISR 再検証
  if (body.slug) {
    const articlePath = `/articles/${body.slug}`;

    // ISR(Router Cache / Data Cache)の再検証
    // 第二引数に "page" を渡すとページ単位で無効化できます。
    revalidatePath(articlePath, 'page');

    pathsToRevalidate.push(articlePath);
  }

  // 記事単位で tag を貼っている場合
  if (body.tags && body.tags.length > 0) {
    for (const tag of body.tags) {
      // 例: fetch に next: { tags: ['article:123'] } を付けているケース
      revalidateTag(tag);
    }
  }

  // CloudFront 側のキャッシュ無効化
  // 単純化のためページパスのみ無効化しています。
  // 必要に応じて `/articles/${slug}` に対応する静的ファイルパスも追加してください。
  await invalidateCloudFrontPaths(pathsToRevalidate);

  return NextResponse.json({
    ok: true,
    revalidatedPaths: pathsToRevalidate,
  });
}

// App Router で確実に都度評価されるよう、動的レンダリングを明示しておきます。
export const dynamic = 'force-dynamic';

CMS 側で設定するWebhookリクエスト例

POST <https://your-domain.com/api/revalidate>
Content-Type: application/json

{
  "secret": "YOUR_WEBHOOK_SECRET",
  "slug": "my-article-slug",
  "tags": ["article:123"]
}

こうしておくと、CMS で記事が更新されたタイミング = ISR と CloudFront の両方のキャッシュを更新するタイミング にできます。エンジニアが毎回手動でキャッシュクリアを行う必要もありません。

まとめ

Next.js の ISR を AWS 上で運用する場合、複数のサービスを適切に組み合わせる必要がありますが、SST v3 を利用するとその構成が自然な形で整理され、デプロイや動作確認も扱いやすくなります。

この記事では、Next.js の環境構築、ISR ページの作成、SST v3 の設定とデプロイ、CloudFront を経由した更新確認、SST Console を使った状態把握までを順に整理しました。ISR の特徴と AWS のキャッシュ構造を理解して構築することで、長期的な運用に耐えるサイト構成を整えることができます。

Next.jsでの構築・開発なら株式会社デパートにおまかせ

株式会社デパートでは、Next.jsを利用したシステム設計、開発を一貫して対応可能です。

システム設計・開発サービスをご紹介

Contact

制作のご依頼やサービスに関するお問い合わせ、
まだ案件化していないご相談など、
お気軽にお問い合わせください。

お問い合わせはこちら

関連ブログ