12k
All articles

Next.jsにおけるスマートなキャッシング:部分レンダリングと再利用可能なコンポーネント

Next.js App RouterにおけるData Cache、Full Route Cache、Partial Prerenderingのキャッシングは、サーバーコンポーネントにキャッシング方針を持たせることで予測可能になる。

OpenReplay Team
OpenReplay Team
Next.jsにおけるスマートなキャッシング:部分レンダリングと再利用可能なコンポーネント

Next.jsのApp Routerアプリケーションを構築しました。データフェッチは動作し、ページもレンダリングされます。しかし、キャッシング戦略が正しいのか、そもそも戦略があるのかさえ不明です。予期せず古いデータが表示されたり、同じデータベースクエリがリクエストごとに複数回実行されるのを目撃したり、「静的」であるはずのルートが遅く感じられる理由を疑問に思ったことがあるでしょう。

この記事では、Next.js App Routerのデータキャッシングが実際にどのように機能するか、3つのキャッシュレイヤーがどのように相互作用するか、そしてデータフェッチとキャッシングポリシーの両方をカプセル化した再利用可能なサーバーコンポーネントの構築方法を説明します。また、Next.jsの部分プリレンダリング(Partial Prerendering)と、React 19の部分レンダリングがコンポーネントレベルのキャッシング戦略をどのように可能にするかについても取り上げます。

重要なポイント

  • Next.jsは3つのキャッシュレイヤー(Data Cache、Full Route Cache、Router Cache)を使用し、リクエスト時にカスケード的に連携する
  • コンポーネントは、Reactのcache()を使用した重複排除とfetchオプションによるライフタイム制御により、独自のキャッシングポリシーを持つべき
  • タグベースの再検証により、複数のルートにわたる外科的なキャッシュ無効化が可能
  • 部分プリレンダリング(PPR)は、Suspense境界を通じて静的シェルと動的ストリーミングコンテンツを混在させることができる

3つのキャッシュレイヤーの連携方法

Next.jsのキャッシングは、すべてのリクエスト中に相互作用する3つの異なるメカニズムを通じて動作します。

Data Cache(データキャッシュ)

Data Cacheは、リクエストとデプロイメント全体でフェッチ結果を永続化します。キャッシングを有効にしてfetchを呼び出すと、Next.jsはレスポンスをサーバー側に保存します。後続のリクエストは、再検証が発生するまでキャッシュされたデータを返します。

// すべてのリクエストで1時間キャッシュ
const posts = await fetch('https://api.example.com/posts', {
  next: { revalidate: 3600 }
})

Full Route Cache(完全ルートキャッシュ)

ビルド時に、Next.jsは静的ルートをHTMLとRSCペイロードにレンダリングします。このFull Route Cacheは、プリレンダリングされたコンテンツを即座に提供します。完全に動的なルートはこのキャッシュをスキップしますが、静的セグメントまたは再検証を持つルートは静的シェルを生成できます。

Client-side Router Cache(クライアント側ルーターキャッシュ)

ブラウザは、ナビゲーション中にRSCペイロードをメモリに保存します。レイアウトはルート変更をまたいで永続化されます。訪問済みルートはセッション中メモリにキャッシュされ、無効化されるまでナビゲーション中に再利用されます。

これらのレイヤーはカスケードします:Data Cacheを無効化すると後続のリクエストに影響し、Full Route CacheまたはRouter Cacheは新しいデータを必要とする次のレンダリング時に更新されます。

再利用可能なキャッシュ済みサーバーコンポーネントの構築

古いデータのバグを防ぐメンタルモデル:コンポーネントは独自のキャッシングポリシーを持つべきです。

// lib/data.ts
import { cache } from 'react'

export const getProduct = cache(async (id: string) => {
  const res = await fetch(`https://api.example.com/products/${id}`, {
    next: { revalidate: 300, tags: ['products', `product-${id}`] }
  })
  return res.json()
})

この関数は、レイアウト、ページ、またはネストされた子コンポーネントなど、あらゆるコンポーネントから呼び出すことができます。Reactのcache()は、単一のレンダリングパス内での呼び出しを重複排除します。next.revalidateオプションは、Data Cacheのライフタイムを制御します。タグは外科的な無効化を可能にします。

この関数はpropsのドリリングなしでどこでも使用できます:

// app/products/[id]/page.tsx
export default async function ProductPage({ params }: { params: { id: string } }) {
  const { id } = await params
  const product = await getProduct(id)
  return <ProductDetails product={product} />
}

ルートセグメントオプションによるキャッシュ動作の制御

fetchオプションに加えて、ルートセグメント設定はルートレベルでキャッシングを制御します:

// 動的レンダリングを強制
export const dynamic = 'force-dynamic'

// すべてのフェッチの再検証期間を設定
export const revalidate = 3600

オンデマンド無効化には、revalidatePathまたはタグベースの再検証を使用します:

// app/actions.ts
'use server'
import { revalidateTag } from 'next/cache'

export async function updateProduct(id: string) {
  // await db.product.update(...)
  revalidateTag(`product-${id}`)
}

Next.js部分プリレンダリング:実験的な方向性

Next.jsの部分プリレンダリング(Partial Prerendering)は重要な転換を表しています。完全に静的または完全に動的なルートのどちらかを選択する代わりに、PPRは静的シェルをプリレンダリングしながら、Suspense境界を通じて動的コンテンツをストリーミングします。

import { Suspense } from 'react'

export default async function ProductPage({ params }: { params: { id: string } }) {
  const { id } = await params
  
  return (
    <>
      {/* 静的シェル - プリレンダリング済み */}
      <Header />
      <ProductInfo id={id} />
      
      {/* 動的ホール - リクエスト時にストリーミング */}
      <Suspense fallback={<CartSkeleton />}>
        <UserCart />
      </Suspense>
    </>
  )
}

静的部分は即座に提供されます。動的セクションは解決されるとストリーミングされます。この機能はまだ実験的です—next.config.jsのexperimentalオプションでppr: trueを設定して有効化します—が、Next.jsキャッシングの未来を示しています。

React 19とコンポーネントレベルのキャッシング

React 19の改善されたSuspense動作により、より細かいキャッシング戦略が可能になります。Suspenseでラップされたコンポーネントは、独立してデータライフサイクルを管理できます。実験的なuse cacheおよびcacheLife APIを使用することで、ページ全体ではなくコンポーネントサブツリーをキャッシュできます:

import { cacheLife } from 'next/cache'

async function BlogPosts() {
  'use cache'
  cacheLife('hours')
  
  const res = await fetch('https://api.example.com/posts')
  const posts = await res.json()
  return <PostList posts={posts} />
}

タグベースの再検証により、チームはルート間でキャッシュされたデータを安全に共有できます。/products/[id]/checkoutの両方で使用される製品コンポーネントは、一度無効化するだけで、すべての場所で更新されます。

まとめ

キャッシングポリシーをカプセル化したサーバーコンポーネントを構築しましょう。リクエストの重複排除にはReactのcache()を、Data Cacheの制御にはfetchオプションを、ルート間の無効化にはタグを使用します。部分プリレンダリングは実験的ですが、理解する価値があります—それがNext.jsキャッシングの向かう方向です。

目標は最大限のキャッシングではありません。データの実際の鮮度要件に合致した予測可能なキャッシングです。

よくある質問

Reactのcache()とNext.jsのfetchキャッシングの違いは何ですか?

Reactのcache()は単一のレンダリングパス内で関数呼び出しを重複排除し、1つのリクエスト中に同じデータが複数回フェッチされるのを防ぎます。Next.jsのfetchキャッシングは、Data Cacheを使用して複数のリクエストとデプロイメント全体で結果を永続化します。両方を組み合わせて使用します:レンダリング時の重複排除にはcache()を、リクエスト間の永続化にはfetchオプションを使用します。

revalidateTagとrevalidatePathをいつ使用すべきですか?

詳細ページとチェックアウトページの両方に表示される製品情報など、複数のルートにわたって特定のデータを無効化する必要がある場合はrevalidateTagを使用します。特定のURLパスのすべてのキャッシュデータを無効化したい場合はrevalidatePathを使用します。タグはより外科的な制御を提供し、パスは単一ルートの無効化にはシンプルです。

ルートがキャッシュされているか動的にレンダリングされているかをどのように判断できますか?

ビルド出力でルートインジケーターを確認してください。静的ルートは円形アイコンを表示し、動的ルートはラムダ記号を表示します。コンポーネントにconsole.log文を追加することもできます—リクエストごとにログが記録される場合、ルートは動的です。cookies()、headers()、またはsearchParamsを使用すると、ルートは自動的に動的レンダリングにオプトインされます。

部分プリレンダリングは本番環境で使用できますか?

部分プリレンダリングは、Next.js 15の時点ではまだ実験的です。テスト用に有効化することはできますが、本番アプリケーションではまだ推奨されていません。APIと動作は将来のリリースで変更される可能性があります。本番環境で採用する前に、Next.jsのドキュメントとリリースノートで安定性の更新を監視してください。

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.