コンテンツにスキップ

use cache: remote

この機能は現在、カナリアチャンネルで利用可能であり、変更される可能性があります。Next.jsのアップグレードで試してみて、GitHubでフィードバックを共有してください。

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

知っておくと良いこと

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

使用方法

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

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

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

基本例

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

app/product/[id]/page.tsx
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 }) {
  // Calling connection() makes this component dynamic, preventing
  // it from being included in the static shell. This ensures the price
  // is always fetched at request time.
  await connection()
 
  // Now we can cache the price in a remote cache handler.
  // Regular 'use cache' would NOT work here because we're in a dynamic context.
  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 hour
 
  // This database query is cached and shared across all users
  return db.products.getPrice(productId)
}

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

use cache: remote は use cache および use cache: private とどのように異なりますか

Next.js は、それぞれ異なるユースケース向けに設計された 3 つのキャッシング ディレクティブを提供します。

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

注意: '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. キャッシュの無効化は、cacheTagrevalidateTag を介して機能します。
  4. キャッシュの有効期限は、cacheLife 構成によって制御されます。

動的なコンテキストの例

async function UserDashboard() {
  // Calling connection() makes the context dynamic
  await connection()
 
  // Without any caching directive, this runs on every request
  const stats = await getStats()
 
  // With 'use cache: remote', this is cached in the remote handler
  const analytics = await getAnalytics()
 
  return (
    <div>
      <Stats data={stats} />
      <Analytics data={analytics} />
    </div>
  )
}
 
async function getAnalytics() {
  'use cache: remote'
  cacheLife({ expire: 300 }) // 5 minutes
 
  // This expensive operation is cached and shared across all requests
  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')の中にネストできません
  • プライベートキャッシュは、リモートキャッシュの中にネストできません
// VALID: Remote inside remote
async function outerRemote() {
  'use cache: remote'
  const result = await innerRemote()
  return result
}
 
async function innerRemote() {
  'use cache: remote'
  return getData()
}
 
// VALID: Remote inside regular cache
async function outerCache() {
  'use cache'
  // If this is in a dynamic context, the inner remote cache will work
  const result = await innerRemote()
  return result
}
 
async function innerRemote() {
  'use cache: remote'
  return getData()
}
 
// INVALID: Remote inside private
async function outerPrivate() {
  'use cache: private'
  const result = await innerRemote() // Error!
  return result
}
 
async function innerRemote() {
  'use cache: remote'
  return getData()
}
 
// INVALID: Private inside remote
async function outerRemote() {
  'use cache: remote'
  const result = await innerPrivate() // Error!
  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() {
  // Make context dynamic
  await connection()
 
  const stats = await getGlobalStats()
 
  return <StatsDisplay stats={stats} />
}
 
async function getGlobalStats() {
  'use cache: remote'
  cacheTag('global-stats')
  cacheLife({ expire: 60 }) // 1 minute
 
  // This expensive database query is cached and shared across all users,
  // reducing load on your database
  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() {
  // Dynamic context
  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 minutes
 
  // This API call is cached, reducing requests to your external service
  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() {
  // Dynamic security check
  await connection()
 
  const report = await generateReport()
 
  return <ReportViewer report={report} />
}
 
async function generateReport() {
  'use cache: remote'
  cacheLife({ expire: 3600 }) // 1 hour
 
  // This expensive computation is cached and shared across all authorized users,
  // avoiding repeated calculations
  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'
 
// Static product data - prerendered at build time
async function getProduct(id: string) {
  'use cache'
  cacheTag(`product-${id}`)
 
  // This is cached at build time and shared across all users
  return db.products.find({ where: { id } })
}
 
// Shared pricing data - cached at runtime in remote handler
async function getProductPrice(id: string) {
  'use cache: remote'
  cacheTag(`product-price-${id}`)
  cacheLife({ expire: 300 }) // 5 minutes
 
  // This is cached at runtime and shared across all users
  return db.products.getPrice({ where: { id } })
}
 
// User-specific recommendations - private cache per user
async function getRecommendations(productId: string) {
  'use cache: private'
  cacheLife({ expire: 60 }) // 1 minute
 
  const sessionId = (await cookies()).get('session-id')?.value
 
  // This is cached per-user and never shared
  return db.recommendations.findMany({
    where: { productId, sessionId },
  })
}
 
export default async function ProductPage({ params }) {
  const { id } = await params
 
  // Static product data
  const product = await getProduct(id)
 
  return (
    <div>
      <ProductDetails product={product} />
 
      {/* Dynamic shared price */}
      <Suspense fallback={<PriceSkeleton />}>
        <ProductPriceComponent productId={id} />
      </Suspense>
 
      {/* Dynamic personalized recommendations */}
      <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 }) {
  // Make this component dynamic
  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' は実験的な機能として導入されました。