next-cache-components

next-cache-components

熱門

Next.js 16 快取元件 - PPR、use cache 指令、cacheLife、cacheTag、updateTag

942星標
75分支
更新於 2026/6/21
SKILL.md
readonlyread-only
name
next-cache-components
description

Next.js 16 快取元件 - PPR、use cache 指令、cacheLife、cacheTag、updateTag

快取元件(Next.js 16+)

快取元件可啟用部分預先渲染(PPR)——在同一路由中混合靜態、快取和動態內容。

啟用快取元件

// next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  cacheComponents: true,
}

export default nextConfig

這取代了舊的 experimental.ppr 標記。


三種內容類型

啟用快取元件後,內容分為三類:

1. 靜態(自動預先渲染)

同步程式碼、匯入、純計算——在建置時預先渲染:

export default function Page() {
  return (
    <header>
      <h1>我們的部落格</h1>  {/* 靜態 - 即時 */}
      <nav>...</nav>
    </header>
  )
}

2. 快取(use cache

不需要每次請求都重新擷取的非同步資料:

async function BlogPosts() {
  'use cache'
  cacheLife('hours')

  const posts = await db.posts.findMany()
  return <PostList posts={posts} />
}

3. 動態(Suspense)

必須保持最新的執行時期資料——包在 Suspense 中:

import { Suspense } from 'react'

export default function Page() {
  return (
    <>
      <BlogPosts />  {/* 快取 */}

      <Suspense fallback={<p>載入中...</p>}>
        <UserPreferences />  {/* 動態 - 串流載入 */}
      </Suspense>
    </>
  )
}

async function UserPreferences() {
  const theme = (await cookies()).get('theme')?.value
  return <p>主題:{theme}</p>
}

use cache 指令

檔案層級

'use cache'

export default async function Page() {
  // 整個頁面被快取
  const data = await fetchData()
  return <div>{data}</div>
}

元件層級

export async function CachedComponent() {
  'use cache'
  const data = await fetchData()
  return <div>{data}</div>
}

函式層級

export async function getData() {
  'use cache'
  return db.query('SELECT * FROM posts')
}

快取設定檔

內建設定檔

'use cache'                    // 預設:5 分鐘過期,15 分鐘重新驗證
'use cache: remote'           // 平台提供的快取(Redis、KV)
'use cache: private'          // 用於合規,允許執行時期 API

cacheLife() - 自訂生命週期

import { cacheLife } from 'next/cache'

async function getData() {
  'use cache'
  cacheLife('hours')  // 內建設定檔
  return fetch('/api/data')
}

內建設定檔:'default''minutes''hours''days''weeks''max'

內嵌設定

async function getData() {
  'use cache'
  cacheLife({
    stale: 3600,      // 1 小時 - 在重新驗證期間提供過期內容
    revalidate: 7200, // 2 小時 - 背景重新驗證間隔
    expire: 86400,    // 1 天 - 硬性過期
  })
  return fetch('/api/data')
}

快取失效

cacheTag() - 標記快取內容

import { cacheTag } from 'next/cache'

async function getProducts() {
  'use cache'
  cacheTag('products')
  return db.products.findMany()
}

async function getProduct(id: string) {
  'use cache'
  cacheTag('products', `product-${id}`)
  return db.products.findUnique({ where: { id } })
}

updateTag() - 立即失效

當你需要在同一請求中重新整理快取時使用:

'use server'

import { updateTag } from 'next/cache'

export async function updateProduct(id: string, data: FormData) {
  await db.products.update({ where: { id }, data })
  updateTag(`product-${id}`)  // 立即 - 同一請求看到最新資料
}

revalidateTag() - 背景重新驗證

用於過期-重新驗證行為:

'use server'

import { revalidateTag } from 'next/cache'

export async function createPost(data: FormData) {
  await db.posts.create({ data })
  revalidateTag('posts')  // 背景 - 下一個請求看到最新資料
}

執行時期資料限制

不能use cache 內部存取 cookies()headers()searchParams

解決方案:作為引數傳入

// 錯誤 - 在 use cache 內部使用執行時期 API
async function CachedProfile() {
  'use cache'
  const session = (await cookies()).get('session')?.value  // 錯誤!
  return <div>{session}</div>
}

// 正確 - 在外部提取,作為引數傳入
async function ProfilePage() {
  const session = (await cookies()).get('session')?.value
  return <CachedProfile sessionId={session} />
}

async function CachedProfile({ sessionId }: { sessionId: string }) {
  'use cache'
  // sessionId 自動成為快取鍵的一部分
  const data = await fetchUserData(sessionId)
  return <div>{data.name}</div>
}

例外:use cache: private

用於無法重構的合規需求:

async function getData() {
  'use cache: private'
  const session = (await cookies()).get('session')?.value  // 允許
  return fetchData(session)
}

快取鍵產生

快取鍵根據以下內容自動產生:

  • 建置 ID - 部署時使所有快取失效
  • 函式 ID - 函式位置的雜湊值
  • 可序列化引數 - props 成為鍵的一部分
  • 閉包變數 - 包含外部作用域的值
async function Component({ userId }: { userId: string }) {
  const getData = async (filter: string) => {
    'use cache'
    // 快取鍵 = userId(閉包)+ filter(引數)
    return fetch(`/api/users/${userId}?filter=${filter}`)
  }
  return getData('active')
}

完整範例

import { Suspense } from 'react'
import { cookies } from 'next/headers'
import { cacheLife, cacheTag } from 'next/cache'

export default function DashboardPage() {
  return (
    <>
      {/* 靜態外殼 - 從 CDN 即時提供 */}
      <header><h1>儀表板</h1></header>
      <nav>...</nav>

      {/* 快取 - 快速,每小時重新驗證 */}
      <Stats />

      {/* 動態 - 串流載入最新資料 */}
      <Suspense fallback={<NotificationsSkeleton />}>
        <Notifications />
      </Suspense>
    </>
  )
}

async function Stats() {
  'use cache'
  cacheLife('hours')
  cacheTag('dashboard-stats')

  const stats = await db.stats.aggregate()
  return <StatsDisplay stats={stats} />
}

async function Notifications() {
  const userId = (await cookies()).get('userId')?.value
  const notifications = await db.notifications.findMany({
    where: { userId, read: false }
  })
  return <NotificationList items={notifications} />
}

從舊版本遷移

舊設定 取代方式
experimental.ppr cacheComponents: true
dynamic = 'force-dynamic' 移除(預設行為)
dynamic = 'force-static' 'use cache' + cacheLife('max')
revalidate = N cacheLife({ revalidate: N })
unstable_cache() 'use cache' 指令

unstable_cache 遷移到 use cache

unstable_cache 已在 Next.js 16 中被 use cache 指令取代。啟用 cacheComponents 後,將 unstable_cache 呼叫轉換為 use cache 函式:

之前(unstable_cache):

import { unstable_cache } from 'next/cache'

const getCachedUser = unstable_cache(
  async (id) => getUser(id),
  ['my-app-user'],
  {
    tags: ['users'],
    revalidate: 60,
  }
)

export default async function Page({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params
  const user = await getCachedUser(id)
  return <div>{user.name}</div>
}

之後(use cache):

import { cacheLife, cacheTag } from 'next/cache'

async function getCachedUser(id: string) {
  'use cache'
  cacheTag('users')
  cacheLife({ revalidate: 60 })
  return getUser(id)
}

export default async function Page({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params
  const user = await getCachedUser(id)
  return <div>{user.name}</div>
}

主要差異:

  • 無需手動快取鍵 - use cache 根據函式引數和閉包自動產生鍵。不再需要 unstable_cachekeyParts 陣列。
  • 標籤 - 用函式內部的 cacheTag() 呼叫取代 options.tags
  • 重新驗證 - 用 cacheLife({ revalidate: N }) 或內建設定檔(如 cacheLife('minutes'))取代 options.revalidate
  • 動態資料 - unstable_cache 不支援在回呼中使用 cookies()headers()use cache 也有相同限制,但必要時可使用 'use cache: private'

限制

  • 不支援 Edge 執行時期 - 需要 Node.js
  • 不支援靜態匯出 - 需要伺服器
  • 非確定性值Math.random()Date.now())在 use cache 內部僅在建置時執行一次

若需在快取外部取得請求時期的隨機性:

import { connection } from 'next/server'

async function DynamicContent() {
  await connection()  // 延遲到請求時期
  const id = crypto.randomUUID()  // 每次請求不同
  return <div>{id}</div>
}

來源: