use cache: remote
'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 フラグを有効にします。
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
}
export default nextConfig次に、データを動的なコンテキストでキャッシュする必要がある関数に 'use cache: remote' を追加します。
基本例
リクエスト時に取得する必要があるが、すべてのユーザー間で共有できる製品価格をキャッシュします。cacheLife を使用して、価格のキャッシュ有効期間を設定します。
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 に遭遇すると、コンテキストは「動的」になります。動的なコンテキストでは、
- 通常の
use cacheは機能しなくなります - 何もキャッシュしません。 'use cache: remote'は引き続き機能します - リモートキャッシュハンドラーによってキャッシュされます。- 結果はサーバーサイドに保存されます - デプロイメント用に構成されたキーバリューストアに保存されます。
- キャッシュされたデータはリクエスト間で共有されます - データベースの負荷とオリジンリクエストを削減します。
知っておくと良いこと:
'use cache: remote'なしでは、動的なコンテキストの関数はリクエストごとに実行され、パフォーマンスのボトルネックが発生する可能性があります。リモートキャッシュは、結果をサーバーサイドのキャッシュハンドラーに保存することで、この問題を解消します。
ストレージの動作
リモートキャッシュはサーバーサイドのキャッシュハンドラーを使用して永続化されます。これには以下が含まれる場合があります。
- 分散キーバリューストア(インメモリまたは永続ストレージソリューション)
- ファイルシステムまたはインメモリストレージ(開発中またはカスタムデプロイメントでよく使用されます)
- 環境固有のキャッシュ(ホスティングインフラストラクチャによって提供されます)
- カスタムまたは構成済みのキャッシュハンドラー(アプリケーションの設定によって異なります)
これは意味します
- キャッシュされたデータは、すべてのユーザーとリクエストで共有されます。
- キャッシュエントリは、単一のセッションを超えて持続します。
- キャッシュの無効化は、
cacheTagとrevalidateTagを介して機能します。 - キャッシュの有効期限は、
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 にアクセスすることを技術的に許可しますが、一般的にこれらを一緒に使用することは推奨されません。
| API | use 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 パラメータ(stale、revalidate、expire)の詳細については、cacheLife API リファレンスを参照してください。
リクエストごとのデータベースクエリ
動的なコンテキストでアクセスされる高コストなデータベースクエリをキャッシュし、データベースの負荷を軽減します。
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 レスポンスをキャッシュします。
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()
}動的なチェック後の計算済みデータ
動的なセキュリティまたは機能チェックの後に発生する高コストな計算をキャッシュします。
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),
}
}混合キャッシング戦略
最適なパフォーマンスのために、静的、リモート、およびプライベートキャッシングを組み合わせます。
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' は実験的な機能として導入されました。 |
関連
use cache
use cache: private
cacheComponents
cacheLife
cacheTag
connection
役に立ちましたか?