缓存

缓存是把「计算结果」存下来,下次直接复用。Next.js 在 App Router 上引入了 Cache Components 模型,用 use cache 指令显式声明可以缓存的部分,其它需要运行时数据的部分则用 <Suspense> 包起来。整体思路叫 Partial Prerendering (PPR)

启用 Cache Components

next.config.ts 开启:

import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  cacheComponents: true,
};

export default nextConfig;

use cache 指令

use cache 缓存异步函数或组件的返回值。可以放在两个粒度:

数据级缓存

import { cacheLife } from 'next/cache';

export async function getUsers() {
  'use cache';
  cacheLife('hours');
  return db.query('SELECT * FROM users');
}

函数的 参数 和闭包变量会自动成为缓存 key 的一部分,因此不同入参会产生不同的缓存条目。

UI 级缓存

整个组件/页面都可以缓存:

import { cacheLife } from 'next/cache';

export default async function Page() {
  'use cache';
  cacheLife('hours');

  const users = await db.query('SELECT * FROM users');
  return (
    <ul>
      {users.map(u => <li key={u.id}>{u.name}</li>)}
    </ul>
  );
}

如果在文件顶部写 'use cache',该文件里所有导出都会被缓存。

流式未缓存的数据

对于每次请求都要最新的数据(比如调用 API 或数据库),不要'use cache',用 <Suspense> 包起来即可:

import { Suspense } from 'react';

async function LatestPosts() {
  const data = await fetch('https://api.example.com/posts');
  const posts = await data.json();
  return <ul>{posts.map((p: any) => <li key={p.id}>{p.title}</li>)}</ul>;
}

export default function Page() {
  return (
    <>
      <h1>我的博客</h1>
      <Suspense fallback={<p>加载最新文章…</p>}>
        <LatestPosts />
      </Suspense>
    </>
  );
}

fallback 会进入静态 HTML 外壳,真实内容在请求时流式注入。

运行时 API

cookiesheaderssearchParamsparams(除非提供了 generateStaticParams)只能在请求时拿到。访问它们的组件 必须包在 <Suspense>

import { cookies } from 'next/headers';
import { Suspense } from 'react';

async function UserGreeting() {
  const theme = (await cookies()).get('theme')?.value || 'light';
  return <p>你的主题:{theme}</p>;
}

export default function Page() {
  return (
    <Suspense fallback={<p>加载中…</p>}>
      <UserGreeting />
    </Suspense>
  );
}

把运行时值喂给缓存函数

从运行时拿到值后,可以当参数传给 use cache 函数:

import { cookies } from 'next/headers';
import { Suspense } from 'react';

async function CachedContent({ sessionId }: { sessionId: string }) {
  'use cache';
  return <div>{await fetchUserData(sessionId)}</div>;
}

async function ProfileContent() {
  const session = (await cookies()).get('session')?.value;
  return <CachedContent sessionId={session!} />;
}

export default function Page() {
  return (
    <Suspense fallback={<div>加载中…</div>}>
      <ProfileContent />
    </Suspense>
  );
}

非确定性操作

Math.random()Date.now()crypto.randomUUID() 每次结果不同。想每次请求都给不同值?调 connection() 让渲染下沉到请求时:

import { connection } from 'next/server';
import { Suspense } from 'react';

async function Unique() {
  await connection();
  return <p>请求 ID: {crypto.randomUUID()}</p>;
}

export default function Page() {
  return (
    <Suspense fallback={<p>加载中…</p>}>
      <Unique />
    </Suspense>
  );
}

Partial Prerendering 概览

Cache Components 开启后,每条路由在构建期会生成一个 静态 shell

  • 'use cache' 的结果进入 shell。
  • <Suspense> 里未缓存的部分在请求时流式填入。
  • 纯计算(同步 I/O、模块导入)被自动放入 shell。

这就是默认的 PPR 渲染行为,同一条路由能同时做到「首字节超快 + 个性化最新」。

快取

快取是把「計算結果」儲存起來,下次直接重用。Next.js 在 App Router 中引入了 Cache Components 模型:用 use cache 指令顯式聲明可快取的部分,其餘需要執行時資料的部分則用 <Suspense> 包起來。整體稱為 Partial Prerendering (PPR)

啟用 Cache Components

next.config.ts 啟用:

import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  cacheComponents: true,
};

export default nextConfig;

use cache 指令

use cache 會快取非同步函式或元件的回傳值,可用於兩個層級:

資料級快取

import { cacheLife } from 'next/cache';

export async function getUsers() {
  'use cache';
  cacheLife('hours');
  return db.query('SELECT * FROM users');
}

函式的 參數 與閉包變數會自動成為 cache key 的一部分,因此不同入參會產生不同的快取條目。

UI 級快取

整個元件/頁面都可以被快取:

import { cacheLife } from 'next/cache';

export default async function Page() {
  'use cache';
  cacheLife('hours');

  const users = await db.query('SELECT * FROM users');
  return (
    <ul>
      {users.map(u => <li key={u.id}>{u.name}</li>)}
    </ul>
  );
}

若在檔案頂部寫 'use cache',該檔案所有匯出皆會被快取。

串流未快取的資料

對於每次請求都需最新的資料(如呼叫 API 或資料庫),切勿'use cache',改用 <Suspense> 包住:

import { Suspense } from 'react';

async function LatestPosts() {
  const data = await fetch('https://api.example.com/posts');
  const posts = await data.json();
  return <ul>{posts.map((p: any) => <li key={p.id}>{p.title}</li>)}</ul>;
}

export default function Page() {
  return (
    <>
      <h1>我的網誌</h1>
      <Suspense fallback={<p>載入最新文章…</p>}>
        <LatestPosts />
      </Suspense>
    </>
  );
}

fallback 會進入靜態 HTML 外殼,真實內容於請求時串流注入。

執行時 API

cookiesheaderssearchParamsparams(除非提供了 generateStaticParams)只能在請求時取得。存取它們的元件 必須包在 <Suspense>

import { cookies } from 'next/headers';
import { Suspense } from 'react';

async function UserGreeting() {
  const theme = (await cookies()).get('theme')?.value || 'light';
  return <p>你的主題:{theme}</p>;
}

export default function Page() {
  return (
    <Suspense fallback={<p>載入中…</p>}>
      <UserGreeting />
    </Suspense>
  );
}

將執行時值傳入快取函式

取得執行時值後,可作為參數傳給 use cache 函式:

import { cookies } from 'next/headers';
import { Suspense } from 'react';

async function CachedContent({ sessionId }: { sessionId: string }) {
  'use cache';
  return <div>{await fetchUserData(sessionId)}</div>;
}

async function ProfileContent() {
  const session = (await cookies()).get('session')?.value;
  return <CachedContent sessionId={session!} />;
}

export default function Page() {
  return (
    <Suspense fallback={<div>載入中…</div>}>
      <ProfileContent />
    </Suspense>
  );
}

非確定性操作

Math.random()Date.now()crypto.randomUUID() 每次結果不同。若要每次請求都給不同值?呼叫 connection() 讓渲染下沉到請求時:

import { connection } from 'next/server';
import { Suspense } from 'react';

async function Unique() {
  await connection();
  return <p>請求 ID: {crypto.randomUUID()}</p>;
}

export default function Page() {
  return (
    <Suspense fallback={<p>載入中…</p>}>
      <Unique />
    </Suspense>
  );
}

Partial Prerendering 概覽

啟用 Cache Components 後,每條路由在建置期都會產出一個 靜態 shell

  • 'use cache' 的結果進入 shell。
  • <Suspense> 內未快取的部分於請求時串流填入。
  • 純計算(同步 I/O、模組匯入)自動納入 shell。

這是預設的 PPR 渲染行為,同一條路由同時做到「首位元組超快 + 個性化最新」。

Caching

Caching stores the result of computations so later requests don't repeat the work. Next.js in the App Router uses the Cache Components model: declare cacheable work with use cache, and wrap runtime data in <Suspense>. The combined rendering strategy is Partial Prerendering (PPR).

Enable Cache Components

Turn it on in next.config.ts:

import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  cacheComponents: true,
};

export default nextConfig;

The use cache directive

use cache caches the return value of async functions and components at two levels.

Data-level caching

import { cacheLife } from 'next/cache';

export async function getUsers() {
  'use cache';
  cacheLife('hours');
  return db.query('SELECT * FROM users');
}

The function's arguments and any closed-over values become part of the cache key, so different inputs produce separate cache entries.

UI-level caching

You can cache an entire component, page, or layout:

import { cacheLife } from 'next/cache';

export default async function Page() {
  'use cache';
  cacheLife('hours');

  const users = await db.query('SELECT * FROM users');
  return (
    <ul>
      {users.map(u => <li key={u.id}>{u.name}</li>)}
    </ul>
  );
}

Add 'use cache' at the top of a file and every exported function is cached.

Streaming uncached data

For data that must be fresh on every request (APIs, DBs), do not add 'use cache'. Wrap it in <Suspense>:

import { Suspense } from 'react';

async function LatestPosts() {
  const data = await fetch('https://api.example.com/posts');
  const posts = await data.json();
  return <ul>{posts.map((p: any) => <li key={p.id}>{p.title}</li>)}</ul>;
}

export default function Page() {
  return (
    <>
      <h1>My Blog</h1>
      <Suspense fallback={<p>Loading latest posts…</p>}>
        <LatestPosts />
      </Suspense>
    </>
  );
}

The fallback lands in the static shell; real content streams in at request time.

Runtime APIs

cookies, headers, searchParams, and params (unless generateStaticParams provides a sample) require runtime information. Any component touching them must be wrapped in <Suspense>:

import { cookies } from 'next/headers';
import { Suspense } from 'react';

async function UserGreeting() {
  const theme = (await cookies()).get('theme')?.value || 'light';
  return <p>Your theme: {theme}</p>;
}

export default function Page() {
  return (
    <Suspense fallback={<p>Loading…</p>}>
      <UserGreeting />
    </Suspense>
  );
}

Feeding runtime values to cached functions

Extract a runtime value and pass it as an argument to a cached function:

import { cookies } from 'next/headers';
import { Suspense } from 'react';

async function CachedContent({ sessionId }: { sessionId: string }) {
  'use cache';
  return <div>{await fetchUserData(sessionId)}</div>;
}

async function ProfileContent() {
  const session = (await cookies()).get('session')?.value;
  return <CachedContent sessionId={session!} />;
}

export default function Page() {
  return (
    <Suspense fallback={<div>Loading…</div>}>
      <ProfileContent />
    </Suspense>
  );
}

Non-deterministic operations

Math.random(), Date.now(), and crypto.randomUUID() return different values each time. To get unique values per request, call connection() to defer rendering to request time:

import { connection } from 'next/server';
import { Suspense } from 'react';

async function Unique() {
  await connection();
  return <p>Request ID: {crypto.randomUUID()}</p>;
}

export default function Page() {
  return (
    <Suspense fallback={<p>Loading…</p>}>
      <Unique />
    </Suspense>
  );
}

Partial Prerendering in one picture

With Cache Components enabled, each route produces a static shell at build time:

  • 'use cache' results are baked into the shell.
  • Anything under <Suspense> that touches runtime data streams in at request time.
  • Pure computations (sync I/O, module imports) are included automatically.

That's the default PPR behavior — the same route delivers fast TTFB and up-to-date personalized content.