Menu

use cache: remote

'use cache: remote'ディレクティブは、await connection()await cookies()await headers()の呼び出し後など、通常のuse cacheが機能しない動的コンテキストで、共有データのキャッシングを有効にします。

補足:

  • 結果はサーバー側のキャッシュハンドラーに保存され、すべてのユーザー間で共有されます。
  • await cookies()またはawait headers()に依存するユーザー固有のデータの場合は、代わりに'use cache: private'を使用してください。

使用方法

'use cache: remote'を使用するには、next.config.tsファイルでcacheComponentsフラグを有効にします。

next.config.ts
TypeScript
import type { NextConfig } from 'next'
 
const nextConfig: NextConfig = {
  cacheComponents: true,
}
 
export default nextConfig

次に、動的コンテキストでデータをキャッシングする必要がある関数に'use cache: remote'を追加します。

基本例

リクエスト時に取得する必要があるが、すべてのユーザー間で共有できる商品価格をキャッシュします。cacheLifeを使用して価格のキャッシュ有効期限を設定します。

app/product/[id]/page.tsx
TypeScript
import { Suspense } from 'react'
import { connection } from 'next/server'
import { cacheTag, cacheLife } from 'next/cache'
 
export default async function ProductPage({
  params,
}: {
  params: Promise<{ id: string }>
}) {
  const { id } = await params
 
  return (
    <div>
      <ProductDetails id={id} />
      <Suspense fallback={<div>Loading price...</div>}>
        <ProductPrice productId={id} />
      </Suspense>
    </div>
  )
}
 
function ProductDetails({ id }: { id: string }) {
  return <div>Product: {id}</div>
}
 
async function ProductPrice({ productId }: { productId: string }) {
  // connection()を呼び出すと、このコンポーネントが動的になり、
  // 静的シェルに含まれるのを防ぎます。これにより、価格が
  // 常にリクエスト時に取得されることが保証されます。
  await connection()
 
  // これでリモートキャッシュハンドラーに価格をキャッシュできます。
  // 動的コンテキストにいるため、通常の'use cache'はここでは機能しません。
  const price = await getProductPrice(productId)
 
  return <div>Price: ${price}</div>
}
 
async function getProductPrice(productId: string) {
  'use cache: remote'
  cacheTag(`product-price-${productId}`)
  cacheLife({ expire: 3600 }) // 1時間
 
  // このデータベースクエリはキャッシュされ、すべてのユーザー間で共有されます
  return db.products.getPrice(productId)
}

注意: 通常のuse cacheは、動的コンテキスト(await connection()await cookies()await headers()など、の後)で使用されると何もキャッシュしません。これらのシナリオで実行時キャッシングを有効にするには'use cache: remote'を使用してください。

use cache: remoteuse cacheuse cache: privateの違い

Next.jsは3つのキャッシングディレクティブを提供し、それぞれ異なるユースケースに対応しています。

機能use cache'use cache: remote''use cache: private'
動的コンテキストで機能いいえ(静的コンテキストが必要)はい(動的コンテキスト向け)はい
await cookies()へのアクセスいいえいいえはい
await headers()へのアクセスいいえいいえはい
await connection()の後いいえ(キャッシュされません)いいえいいえ
キャッシュハンドラーに保存はい(サーバー側)はい(サーバー側)いいえ(クライアント側のみ)
キャッシュスコープグローバル(共有)グローバル(共有)ユーザーごと(分離)
ランタイムプリフェッチングをサポートN/A(ビルド時にプリレンダリング)いいえはい(設定時)
ユースケース静的、共有コンテンツ(ビルド時)動的、ランタイムコンテキストの共有コンテンツ(リクエストごと)パーソナライズされたユーザー固有のコンテンツ

注意: 'use cache: remote'内でawait cookies()またはawait headers()を呼び出すことはできませんが、'use cache: remote'でラップされた関数を呼び出す前に値を読み取ることができ、引数はキャッシュキーに含まれます。キャッシュサイズが大幅に増加し、キャッシュヒット率が低下するため、これは推奨されません。

各ディレクティブを使用する場合

ユースケースに基づいて、適切なキャッシングディレクティブを選択します。

use cacheを使用する場合:

  • コンテンツをビルド時にプリレンダリングできる
  • コンテンツがすべてのユーザー間で共有されている
  • コンテンツがリクエスト固有のデータに依存しない

'use cache: remote'を使用する場合:

  • 動的コンテキスト内でキャッシングが必要である
  • コンテンツはユーザー間で共有されているが、リクエストごとにレンダリングする必要がある(await connection()の後)
  • サーバー側キャッシュハンドラーで高コストの操作をキャッシュしたい

'use cache: private'を使用する場合:

  • コンテンツはユーザーごとにパーソナライズされている(クッキーやヘッダーに依存)
  • ユーザー固有のコンテンツのランタイムプリフェッチングが必要である
  • コンテンツがユーザー間で共有されることはない

動作方法

'use cache: remote'ディレクティブは、ビルド時にプリレンダリングする代わりに、結果をサーバー側キャッシュハンドラーに保存することで、動的コンテキストで共有データのランタイムキャッシングを有効にします。

動的コンテキスト検出

Next.jsがconnection()cookies()headers()などの特定のAPIに遭遇すると、コンテキストは「動的」になります。動的コンテキストでは、以下のようになります。

  1. 通常のuse cacheが機能しなくなる - 何もキャッシュされません
  2. 'use cache: remote'は引き続き機能する - リモートキャッシュハンドラーによってキャッシュされます。
  3. 結果はサーバー側のキー値ストアに保存されます
  4. キャッシュされたデータはリクエスト間で共有される - データベース負荷とオリジンリクエストを削減します

補足: 'use cache: remote'がなければ、動的コンテキスト内の関数はすべてのリクエストで実行され、パフォーマンスのボトルネックが発生する可能性があります。リモートキャッシングは、結果をサーバー側キャッシュハンドラーに保存することでこの問題を解決します。

ストレージ動作

リモートキャッシュはサーバー側キャッシュハンドラーを使用して保存され、以下を含む可能性があります。

  • 分散キー値ストア(メモリ内または永続ストレージソリューション)
  • ファイルシステムまたはメモリ内ストレージ(開発時またはカスタムデプロイ向け)
  • 環境固有のキャッシュ(ホスティングインフラストラクチャによって提供)
  • カスタムまたは設定されたキャッシュハンドラー(アプリケーションのセットアップに応じて)

これは以下を意味します。

  1. キャッシュされたデータはすべてのユーザーとリクエスト間で共有されます
  2. キャッシュエントリは単一セッションを超えて持続します
  3. キャッシュ無効化はcacheTagおよびrevalidateTagで機能します
  4. キャッシュ期限はcacheLife設定で制御されます

動的コンテキスト例

async function UserDashboard() {
  // connection()を呼び出すとコンテキストが動的になります
  await connection()
 
  // キャッシングディレクティブなしでは、これはすべてのリクエストで実行されます
  const stats = await getStats()
 
  // 'use cache: remote'では、これはリモートハンドラーでキャッシュされます
  const analytics = await getAnalytics()
 
  return (
    <div>
      <Stats data={stats} />
      <Analytics data={analytics} />
    </div>
  )
}
 
async function getAnalytics() {
  'use cache: remote'
  cacheLife({ expire: 300 }) // 5分
 
  // この高コストの操作はキャッシュされ、すべてのリクエスト間で共有されます
  return fetchAnalyticsData()
}

リクエストAPIとリモートキャッシュ

'use cache: remote'は技術的には、'use cache: remote'でラップされた関数を呼び出す前にcookies()headers()などのAPIを呼び出すことでリクエスト固有のデータへのアクセスを許可していますが、一般的には一緒に使用することは推奨されません。

APIuse cacheで許可'use cache: remote'で許可推奨
cookies()いいえいいえ代わりに'use cache: private'を使用してください
headers()いいえいいえ代わりに'use cache: private'を使用してください
connection()いいえいいえいいえ - これらはキャッシュできません
searchParamsいいえいいえ代わりに'use cache: private'を使用してください

重要: クッキー、ヘッダー、検索パラメータに基づいてキャッシュする必要がある場合は、代わりに'use cache: private'を使用してください。リモートキャッシュはすべてのユーザー間で共有されるため、ユーザー固有のデータをキャッシュすると、異なるユーザーに不正な結果が提供される可能性があります。

ネスティングルール

リモートキャッシュには特定のネスティングルールがあります。

  • リモートキャッシュは他のリモートキャッシュ('use cache: remote')内にネストできます
  • リモートキャッシュは通常のキャッシュ('use cache')内にネストできます
  • リモートキャッシュはプライベートキャッシュ('use cache: private')内にネストできません
  • プライベートキャッシュはリモートキャッシュ内にネストできません
// 有効:リモート内のリモート
async function outerRemote() {
  'use cache: remote'
  const result = await innerRemote()
  return result
}
 
async function innerRemote() {
  'use cache: remote'
  return getData()
}
 
// 有効:通常のキャッシュ内のリモート
async function outerCache() {
  'use cache'
  // これが動的コンテキストにある場合、内部リモートキャッシュが機能します
  const result = await innerRemote()
  return result
}
 
async function innerRemote() {
  'use cache: remote'
  return getData()
}
 
// 無効:プライベート内のリモート
async function outerPrivate() {
  'use cache: private'
  const result = await innerRemote() // エラー!
  return result
}
 
async function innerRemote() {
  'use cache: remote'
  return getData()
}
 
// 無効:リモート内のプライベート
async function outerRemote() {
  'use cache: remote'
  const result = await innerPrivate() // エラー!
  return result
}
 
async function innerPrivate() {
  'use cache: private'
  return getData()
}

以下の例は'use cache: remote'を使用するための一般的なパターンを示しています。cacheLifeパラメータ(stalerevalidateexpire)の詳細は、cacheLife APIリファレンスを参照してください。

リクエストごとのデータベースクエリ

動的コンテキストでアクセスされる高コストのデータベースクエリをキャッシュし、データベースの負荷を軽減します。

app/dashboard/page.tsx
import { connection } from 'next/server'
import { cacheLife, cacheTag } from 'next/cache'
 
export default async function DashboardPage() {
  // コンテキストを動的にします
  await connection()
 
  const stats = await getGlobalStats()
 
  return <StatsDisplay stats={stats} />
}
 
async function getGlobalStats() {
  'use cache: remote'
  cacheTag('global-stats')
  cacheLife({ expire: 60 }) // 1分
 
  // この高コストのデータベースクエリはキャッシュされ、すべてのユーザー間で共有されます
  // データベースの負荷を軽減します
  const stats = await db.analytics.aggregate({
    total_users: 'count',
    active_sessions: 'count',
    revenue: 'sum',
  })
 
  return stats
}

ストリーミングコンテキストでのAPIレスポンス

ストリーミング中または動的操作後に取得されるAPIレスポンスをキャッシュします。

app/feed/page.tsx
import { Suspense } from 'react'
import { connection } from 'next/server'
import { cacheLife, cacheTag } from 'next/cache'
 
export default async function FeedPage() {
  return (
    <div>
      <Suspense fallback={<Skeleton />}>
        <FeedItems />
      </Suspense>
    </div>
  )
}
 
async function FeedItems() {
  // 動的コンテキスト
  await connection()
 
  const items = await getFeedItems()
 
  return items.map((item) => <FeedItem key={item.id} item={item} />)
}
 
async function getFeedItems() {
  'use cache: remote'
  cacheTag('feed-items')
  cacheLife({ expire: 120 }) // 2分
 
  // このAPIコールはキャッシュされ、外部サービスへのリクエストを削減します
  const response = await fetch('https://api.example.com/feed')
  return response.json()
}

動的チェック後の計算データ

動的セキュリティまたは機能チェック後に発生する高コストの計算をキャッシュします。

app/reports/page.tsx
import { connection } from 'next/server'
import { cacheLife } from 'next/cache'
 
export default async function ReportsPage() {
  // 動的セキュリティチェック
  await connection()
 
  const report = await generateReport()
 
  return <ReportViewer report={report} />
}
 
async function generateReport() {
  'use cache: remote'
  cacheLife({ expire: 3600 }) // 1時間
 
  // この高コストの計算はキャッシュされ、権限を持つすべてのユーザー間で共有されます
  // 繰り返しの計算を回避します
  const data = await db.transactions.findMany()
 
  return {
    totalRevenue: calculateRevenue(data),
    topProducts: analyzeProducts(data),
    trends: calculateTrends(data),
  }
}

混合キャッシング戦略

最適なパフォーマンスのために、静的、リモート、プライベートキャッシングを組み合わせます。

app/product/[id]/page.tsx
import { Suspense } from 'react'
import { connection } from 'next/server'
import { cookies } from 'next/headers'
import { cacheLife, cacheTag } from 'next/cache'
 
// 静的商品データ - ビルド時にプリレンダリング
async function getProduct(id: string) {
  'use cache'
  cacheTag(`product-${id}`)
 
  // これはビルド時にキャッシュされ、すべてのユーザー間で共有されます
  return db.products.find({ where: { id } })
}
 
// 共有価格データ - ランタイムでリモートハンドラーにキャッシュ
async function getProductPrice(id: string) {
  'use cache: remote'
  cacheTag(`product-price-${id}`)
  cacheLife({ expire: 300 }) // 5分
 
  // これはランタイムでキャッシュされ、すべてのユーザー間で共有されます
  return db.products.getPrice({ where: { id } })
}
 
// ユーザー固有の推奨事項 - ユーザーごとのプライベートキャッシュ
async function getRecommendations(productId: string) {
  'use cache: private'
  cacheLife({ expire: 60 }) // 1分
 
  const sessionId = (await cookies()).get('session-id')?.value
 
  // これはユーザーごとにキャッシュされ、共有されることはありません
  return db.recommendations.findMany({
    where: { productId, sessionId },
  })
}
 
export default async function ProductPage({ params }) {
  const { id } = await params
 
  // 静的商品データ
  const product = await getProduct(id)
 
  return (
    <div>
      <ProductDetails product={product} />
 
      {/* 動的共有価格 */}
      <Suspense fallback={<PriceSkeleton />}>
        <ProductPriceComponent productId={id} />
      </Suspense>
 
      {/* 動的パーソナライズ推奨事項 */}
      <Suspense fallback={<RecommendationsSkeleton />}>
        <ProductRecommendations productId={id} />
      </Suspense>
    </div>
  )
}
 
function ProductDetails({ product }) {
  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
    </div>
  )
}
 
async function ProductPriceComponent({ productId }) {
  // このコンポーネントを動的にします
  await connection()
 
  const price = await getProductPrice(productId)
  return <div>Price: ${price}</div>
}
 
async function ProductRecommendations({ productId }) {
  const recommendations = await getRecommendations(productId)
  return <RecommendationsList items={recommendations} />
}
 
function PriceSkeleton() {
  return <div>Loading price...</div>
}
 
function RecommendationsSkeleton() {
  return <div>Loading recommendations...</div>
}
 
function RecommendationsList({ items }) {
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  )
}

補足:

  • リモートキャッシュはサーバー側キャッシュハンドラーに保存され、すべてのユーザー間で共有されます
  • リモートキャッシュは通常のuse cacheが失敗する動的コンテキストで機能します
  • cacheTag()およびrevalidateTag()を使用してリモートキャッシュをオンデマンドで無効化します
  • cacheLife()を使用してキャッシュ期限を設定します
  • ユーザー固有のデータの場合は、'use cache: remote'の代わりに'use cache: private'を使用してください
  • リモートキャッシュは、計算またはフェッチされたデータをサーバー側に保存することでオリジン負荷を軽減します

プラットフォームサポート

デプロイオプションサポート
Node.jsサーバーはい
Dockerコンテナはい
静的エクスポートいいえ
アダプターはい

バージョン履歴

バージョン変更
v16.0.0'use cache: remote'が実験的機能として導入されました。