Next.js

概要

Next.jsはReactをベースとしたフロントエンドフレームワークである。開発体験と本番環境におけるパフォーマンスを両立させるための仕組みを提供することにある。これを実現するため、用途に応じて2つの主要な動作モードが用意されている。

開発サーバー (Development Mode)

ローカル環境での開発時に用いるモードであり、npm run dev コマンドで起動する。開発効率の最大化のため、プロダクションより遅い。

  • ホットリロード機能: コードの変更を検知し、ブラウザを自動的にリロードする。これにより、修正結果を即座に確認できる。
  • オンデマンドコンパイル: ページへのアクセスが発生するたびに、その場でTypeScriptからJavaScriptへのコンパイルが実行される。ターミナルに表示される compile: 86ms といったログがこれに該当する。
  • トレードオフ: 頻繁なコード変更に追従できる利便性を持つ一方、リクエストごとにコンパイルが挟まるため、応答速度は本番環境に比べて著しく遅くなる。これは開発効率を優先した結果の、意図された仕様である。

プロダクションビルド (Production Mode)

Webサイトを一般公開する本番環境で用いるモード。npm run build でコードをビルドし、npm run start でサーバーを起動するという2段階のプロセスを踏む。

  • 事前コンパイル: npm run build コマンドを実行することで、アプリケーション全体のコードが事前に一括でコンパイル・最適化される。
  • 高速な配信: npm run start コマンドは、ビルド済みの静的ファイルを配信する役割に特化している。リクエストごとにコンパイルが走ることはないため、極めて高速に応答を返すことが可能となる。
  • トレードオフ: ユーザーへの応答性能を最大化する設計であるため、ホットリロードのような機能は存在しない。コードの変更を反映させるには、再度 npm run build を実行する必要がある。

動作モードの比較

要するに Development Mode は遅いのでプロファイリングするならプロダクションビルドでやらないといけない。

項目開発サーバープロダクションビルド
起動コマンドnpm run devnpm run buildnpm run start
ページ表示速度遅い(150〜260ms程度)速い(数ms〜数十ms)
コンパイルアクセス毎に実行事前に一括で実行
コード変更の反映即時(ホットリロード)再ビルドが必要
主な用途ローカルでの開発本番環境での運用

開発のための知識

.next/dev/lock

同じディレクトリで複数の起動はできない。

3000 と 3002 の同時起動について 同じ .next/ ディレクトリを共有するため、同時起動はできません。 ポートが別でも .next/dev/lock は1つなので、後から起動する方が Unable to acquire lock エラーになります。

ポート3000 (人間用)  ─┐
                      ├→ 両方とも frontend/.next/ を使う → 競合
ポート3002 (テスト用) ─┘

自動テストの前に、人間用もテスト用も落としておかないといけない。

賢くやるより、fuser で強制的に落としたほうが楽

fuser -k 3000/tcp 2>/dev/null; sleep 2; ss -tlnp | grep 3000 || echo "ポート3000のLISTENプロセスなし"
fuser -k 3002/tcp 2>/dev/null; sleep 2; ss -tlnp | grep 3002 || echo "ポート3002のLISTENプロセスなし"

Next.jsにおけるナビゲーションの仕組み

Next.jsでは、ユーザー体験とパフォーマンスのバランスを取るために、2種類のナビゲーション手法を使い分けます。

Loading diagram...

1. ハードナビゲーション (window.location.href / ページ更新)

ブラウザの標準的な遷移機能を利用する仕組みです。

  • 動作フロー:
  1. リクエスト: ブラウザがサーバーへ対象URLのデータを要求します。
  2. 生成: サーバーが最新のHTMLを生成して返します。
  3. 描画: ブラウザが現在の状態をすべて破棄し、受け取ったHTMLで画面全体を描き直します。
  • 特徴: アドレスバーにURLを入力してEnterを押す動作と同等です。画面が一瞬白くなる(マウント解除される)ため体感速度は落ちますが、サーバー上の最新の状態が確実に反映されます。

2. ソフトナビゲーション (router.push() / Linkコンポーネント)

Next.js(JavaScript)がブラウザの履歴API(History API)を制御する仕組みです。

  • 動作フロー:
  1. リクエスト: JavaScriptが遷移先に必要なデータ(RSC Payload)のみをサーバーに要求します。
  2. 差分更新: 画面全体をリロードせず、変更が必要なコンポーネントのみを書き換えます。
  • 特徴: 画面のちらつきがなく、非常に高速で滑らかな遷移が可能です。ただし、**ブラウザ側に保持されるキャッシュ(Router Cache)**の影響を受けやすくなります。

キャッシュによる「不整合」のメカニズム

ご提示いただいた「保存したのに内容が変わらない」という現象は、サーバー側のキャッシュクライアント側(ブラウザ)のキャッシュの役割の違いから生じます。

サーバー側とクライアント側のキャッシュ構造

キャッシュの種類管理場所役割制御方法
Full Route Cacheサーバーrevalidate = 60 等の設定に基づき、HTML/RSCデータを保持するrevalidatePath で削除可能
Router Cacheブラウザ一度訪れたページのデータを一時的にメモリに保存するrouter.refresh() や Server Actions で制御

API(Route Handler)とServer Actions

項目API (Route Handler)Server Actions
呼び出し方fetch("/api/...")関数の実行 await action()
主な戻り値JSON, XML, etc.成功/失敗 または 最新のRSC Payload
キャッシュ同期手動(router.refresh() などが必要)自動(revalidatePath 等と強力に連動)
主な用途外部システムとの連携自アプリ内のフォーム送信・データ更新
セキュリティCORS設定などが必要な場合があるNext.jsによりCSRF保護等が自動適用される

API (Route Handler)

標準的なHTTPプロトコルに基づいた設計です。フロントエンド(Client)とバックエンド(Server)が完全に分離した状態で通信します。

  • 通信方式: クライアントが fetch() を使用して、特定のURL(例: /api/save)にリクエストを送ります。
  • 状態管理: サーバーはデータ(JSONなど)を返却するだけで、クライアント側の画面状態やキャッシュには関与しません。
  • 用途: 外部サービスからのWebHook受信や、モバイルアプリなどNext.js以外からも利用されるエンドポイントの作成に適しています。

Loading diagram...

Server Actions

Next.jsのフレームワークに深く統合された、サーバー実行関数です。クライアントから「関数」として直接呼び出しますが、実行はサーバーで行われます。

  • 通信方式: use server と定義された関数を呼び出すと、Next.jsが自動的にPOSTリクエストを生成します。
  • 状態管理: サーバー側で revalidatePath などを実行すると、そのレスポンスの中に「新しい画面データ(RSC Payload)」が含まれます。
  • 連動性: データの更新と「ブラウザが持つキャッシュの破棄」「画面の再描画」が、1回のリクエスト/レスポンスの中で完結します。

Loading diagram...

Route Handler と Server Actions でキャッシュの処理が全く違う

どちらも同じ関数(next/cache からインポートするもの)を使いますが、「どこで呼ぶか」によって、ブラウザ側のキャッシュ(Router Cache)に対する影響力が全く変わります。

1. revalidatePath の呼び出し場所による比較

項目Route Handlers(API経由)Server Actions 内部
主な呼び出し方fetch('/api/save')<form action={saveAction}> または await saveAction()
サーバー側キャッシュ即座に無効化される即座に無効化される
ブラウザ側キャッシュ無効化されない(手元のメモリに残る)即座に無効化される(最新化される)
再生成のタイミング次のアクセス時(SWR挙動)そのリクエスト内で最新化
Next.jsの動作単なるHTTP通信として処理データ更新 + 画面同期の特殊通信

A. Route Handler(API)で呼ぶ場合

APIを叩いた時、Next.jsは「データの変更」は認識しますが、それをブラウザに伝える手段がありません。

Loading diagram...

B. Server Actions で呼ぶ場合

Server Actions の場合、Next.jsが通信を乗っ取り、レスポンスの中に「キャッシュ破棄命令」と「最新の画面データ」を詰め込みます。

Loading diagram...

Next.js の内部実装では、Server Action のレスポンスヘッダーに特殊な指示(x-action-revalidated など)が含まれます。

  • API(Route Handler): ブラウザは fetch の結果を単なるデータ(JSON等)として受け取ります。ブラウザ側の Next.js ランタイムは、その背後で revalidatePath が呼ばれたことを検知できません。
  • Server Actions: Next.js が提供する action という仕組みを通じて通信するため、サーバー側で revalidatePath が呼ばれると、ブラウザ側の Next.js ランタイムへ「今すぐ /test/FrontPage のキャッシュを捨てて、同封した最新データに差し替えろ」とダイレクトに命令が下ります。

問題が発生するフロー

  • Next.jsの revalidatePath は、サーバー上のキャッシュを即座に最新へ書き換えるのではなく、「現在のキャッシュを期限切れ(Stale)としてマークする」だけです。そのため、変更直後の最初のリクエストには古いデータが返されます。

Loading diagram...

  • revalidatePath はサーバー側のキャッシュを無効化しますが、**「すでにブラウザのメモリに乗っているキャッシュ(Router Cache)」**を強制的に書き換える力は、API経由の呼び出しでは限定的

  • Stale-While-Revalidate (SWR)

    • SWRは、**「とりあえず手元にある古いデータ(Stale)を出しつつ、裏側で最新版に更新(Revalidate)する」**というキャッシュ戦略。サーバーはキャッシュが期限切れだと知っていても、手元にある「古いHTML」を即座に返します。これでユーザーの待ち時間はゼロになります。
  • Next.jsのISR(Incremental Static Regeneration)には、実は2段階の有効期限があります。

    • Revalidate期間 (revalidate = 60): 60秒経ったら「そろそろ更新の準備をしてね」という合図。
    • Stale Time (300秒): サーバーが「どれだけ古くても、この時間内ならキャッシュを使い回していいよ」と判断する強制期間。
  • 今回起きたことのタイムライン

    • 01:09:01: revalidatePath を実行。サーバー内の test/FrontPage に「期限切れ」のフラグが立ちます。
    • 直後のアクセス: 本来ならここで再生成が走るはずですが、Next.js 15/16の next start(本番モード)では、「すでに配信中の古いHTMLをどれだけ維持するか」の最適化が働きます。
    • 01:14:31: stale-time の300秒(5分)が経過したタイミングで、サーバーがようやく「もうこれ以上古いのは出せない」と判断し、バックグラウンドでのHTML生成プロセスを完了させました。

最終更新: 2026-02-26