•13 min read

Next.js14 App Routerのキャッシュを解明する:古いデータのデバッグ

Next.js14 App Routerのキャッシュを解明する:古いデータのデバッグ

TL;DR: Next.jsのApp Routerは、デフォルトでデータを積極的にキャッシュします。これはパフォーマンスには優れていますが、データが更新されない場合にはフラストレーションがたまります。Request Memoization、Data Cache、Full Route Cache、Router Cacheという4つのキャッシュレイヤーを理解することで、アプリケーションのデータフローを予測可能に制御できるようになります。

Next.js Caching Architecture Overview

データベースを更新したのにNext.jsアプリが古いデータを表示し続けるという経験があるなら、あなただけではありません。ほとんどの開発者は、キャッシュに受動的にアプローチします。つまり、「何か遅いからキャッシュを追加する」「何か古いからクリアする」といった具合です。しかし、Next.jsでは複数のキャッシュが同時に動作しており、互いに認識していないことが多いため、このアプローチはうまくいきません。

それでは、各レイヤー、それらを制御する方法、そしてフレームワークとの戦いをやめる方法について詳しく見ていきましょう。

Audio Briefing
0:00 / 0:00

レイヤー1: リクエストメモ化(Request Memoization)

リクエストメモ化(Request Memoization)は、Next.jsで最も誤解されているキャッシュです。なぜなら、永続的なデータキャッシュのように見えますが、そうではないからです。単一のサーバーレンダリング中に、異なるコンポーネントから同じURLとオプションでfetch()を複数回呼び出すと、Next.jsはそれらを単一のHTTPリクエストに重複排除します。

Request Memoization Visualization

これは単一のリクエストの期間中のみ存在します。ユーザー間やページのリロードをまたいで永続化することはありません。これにより、N+1クエリを心配することなく、レイアウト(Layouts)とページ(Pages)で独立してデータをフェッチできます。

// Both components call the same URL: only ONE HTTP request is made
async function Header() {
  const user = await fetch('https://api.example.com/me').then(r => r.json());
  return <header>Welcome, {user.name}</header>;
}

async function Sidebar() {
  const user = await fetch('https://api.example.com/me').then(r => r.json());
  return <aside>Profile: {user.avatar}</aside>;
}

fetch()を使用していない場合(例: PrismaやDrizzleでデータベースを直接呼び出す場合)、同じ重複排除を実現するには、Reactのcache()関数を使用してクエリを手動でメモ化する必要があります。

Advertisement

レイヤー2: 永続的なデータキャッシュ(Persistent Data Cache)

データキャッシュ(Data Cache)は、Pagesルーターから移行する開発者を困惑させるものです。デフォルトでは、Next.js App Routerはすべてのfetch()呼び出しをサーバー上で無期限にキャッシュします。これは、明示的に無効化されない限り、リクエストやデプロイをまたいで永続化します。

Data Cache Persistence Strategy

リアルタイムデータが必要なダッシュボードを構築している場合、この動作をオプトアウトする必要があります。これは、no-storeを使用してフェッチごとに細かく設定できます。

// Opt out of caching entirely for real-time data
const liveData = await fetch('https://api.example.com/metrics', {
  cache: 'no-store'
});

定期的に更新されるデータには、再検証間隔を定義することで増分静的再生成(ISR: Incremental Static Regeneration)を使用します。

// Cache data but refresh it in the background every 60 seconds
const cachedData = await fetch('https://api.example.com/stats', {
  next: { revalidate: 60 }
});

エンタープライズアプリケーションは通常、**オンデマンド再検証(On-Demand Revalidation)**に依存しています。データを無期限にキャッシュし(force-cache)、CMSまたはデータベースが更新されたときにWebhookを介して再検証をトリガーします。これにより、バックグラウンドリソースを無駄にすることなく、即座の更新が保証されます。特定のキャッシュエントリをパージするには、サーバーアクション(Server Actions)またはルートハンドラー(Route Handlers)でrevalidatePath('/route')またはrevalidateTag('my-tag')を使用します。

レイヤー3 & 4: ルートキャッシュとルーターキャッシュ(Route and Router Cache)

**フルルートキャッシュ(Full Route Cache)**は、ビルド時に静的ルートのHTMLとReact Server Component(RSC)ペイロードを保存します。ユーザーがルートにアクセスすると、サーバーは再レンダリングする代わりにキャッシュされたペイロードを即座に返します。このキャッシュは、revalidatePathまたはrevalidateTagを使用してデータを再検証すると自動的に無効化されます。

Client Side Router Cache

**ルーターキャッシュ(Router Cache)**は、クライアント側のインメモリキャッシュです。ユーザーがアプリケーションをナビゲートすると、Next.jsは訪問したルートセグメントをキャッシュします。これは、以前に訪問したページに戻るのが瞬時に行われることを意味します。ただし、クライアント側のミューテーションがすぐに反映されない可能性もあります。その場合は、ブラウザにサーバーから最新のRSCペイロードを強制的にフェッチさせるために、明示的にrouter.refresh()を呼び出す必要があります。

これら4つのレイヤーがどのように相互作用するかを理解することで、パフォーマンスとデータの鮮度を完全に制御できます。どのキャッシュが古いのかを推測するのをやめ、データ戦略を明示的に定義し、Next.jsに重い処理を任せましょう。

関連資料: SSRで苦労している場合は、Next.jsのハイドレーションエラーの修正に関するこのガイドをチェックしてください。

詳細解説: コアメカニズム

表面の下を見ると、基盤となるメカニズムはシステムの複雑な相互作用を明らかにします。現代の開発において、これらのメカニズムを理解することが、初心者とエキスパートを分けるものです。

この実用的な例を考えてみましょう。

// A comprehensive example demonstrating advanced patterns
class ServiceManager {
  constructor() {
    this.services = new Map();
    this.initialized = false;
  }

  register(name, service) {
    if (this.services.has(name)) {
      throw new Error(`Service ${name} already registered`);
    }
    this.services.set(name, service);
  }

  async initializeAll() {
    this.initialized = true;
    for (const [name, service] of this.services) {
      if (typeof service.init === 'function') {
        await service.init();
      }
    }
  }

  get(name) {
    if (!this.initialized) {
      console.warn('Accessing services before initialization');
    }
    return this.services.get(name);
  }
}

このパターンにより、ビジネス要件が変化しても、私たちのアーキテクチャはスケーラブルで堅牢な状態を保つことができます。これは、大規模なアプリケーションで大きな利益をもたらす基本的なアプローチです。

Advertisement

実世界での応用とスケーリング

これを本番環境で実装すると、新たな課題が生じます。並行処理、状態管理、メモリリークを考慮する必要があります。

たとえば、高スループットシステムを扱う場合、あらゆるマイクロ最適化が重要になります。ローカル開発では明らかにならないボトルネックを特定するために、プロファイリングツールに頼ることがよくあります。

上記の図は、アプリケーションが水平にスケールする典型的なデプロイ戦略を示しています。

理解度チェック

こちらもおすすめ

よくある質問

Next.js App Routerは、4つの異なるキャッシュメカニズムで動作します。(1) リクエストメモ化(Request Memoization):単一のサーバーレンダリングパス内で同一のフェッチURLをReactが重複排除します。(2) データキャッシュ(Data Cache):サーバー上の永続的なクロスリクエストおよびクロスデプロイメントのHTTPフェッチキャッシュです。(3) フルルートキャッシュ(Full Route Cache):ビルド時または再検証時にキャッシュされる静的HTMLおよびReact Server Componentペイロードです。(4) クライアントサイドのルーターキャッシュ(Client-side Router Cache):ユーザーのブラウザセッションにキャッシュされるインメモリのRSCペイロードです。
古いデータが表示されるのは、通常、fetch()がデータキャッシュで{ cache: 'force-cache' }をデフォルトとするか、フルルートキャッシュが静的であるためです。これを修正するには、データを変更した後、サーバーアクション内でrevalidatePath('/your-route')またはrevalidateTag('your-tag')を呼び出すか、動的で常に最新のリクエストに対して{ next: { revalidate: 0 } }または{ cache: 'no-store' }を指定します。
revalidatePathは、特定のURLルートパス(およびオプションでそのサブルート)のキャッシュされたすべてのフェッチリクエストとプリレンダリングされたHTMLをパージします。revalidateTagは、{ next: { tags: ['my-tag'] } }でタグ付けされたアプリケーション全体の特定のフェッチ呼び出しのみをパージし、複数の独立したページにわたる粒度の高いデータ再検証を同時に可能にします。
ルーターキャッシュは、ユーザーのタブセッション中(動的ルートの場合は30秒、静的ルートの場合は5分)、以前に訪問およびプリフェッチされたReact Server Componentペイロードをブラウザのメモリに保存します。クライアントルーターにキャッシュされたレイアウトを破棄させ、最新のサーバーコンポーネントを再フェッチさせるには、next/navigationからrouter.refresh()を呼び出します。
ページでcookies()、headers()などの動的関数を呼び出すか、searchParamsを読み取ると、そのルートはリクエスト時に静的フルルートキャッシュからオプトアウトされます。ページはリクエストごとに動的にレンダリングされるものとして扱われますが、ページ内の個々のfetch()リクエストは、no-storeで設定されていない限り、永続的なデータキャッシュにヒットする可能性があります。

関連アーキテクチャガイド

Share this article:

Stay Updated

Get the latest posts delivered straight to your inbox.

Free Developer Utilities

Free In-Browser Developer Tools

Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

Explore Tools
Advertisement
React Server ComponentsとClient Components:徹底解説
react

React Server ComponentsとClient Components:徹底解説

React Server Components(RSC)とClient Componentsのアーキテクチャ上の違いを理解し、現代のWeb開発で最適なパフォーマンスとインタラクティブ性を実現するためにそれぞれをいつ使用すべきかを学びましょう。

Read more
Next.js App Router動的再検証ガイド
nextjs

Next.js App Router動的再検証ガイド

Next.js App Routerのキャッシュアーキテクチャ、fetchリクエストのメモ化、revalidateTagによるデータキャッシュ無効化、オンデマンドISR再検証を習得しましょう。

Read more