sanity-live-cache-components

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Sanity Live + Cache Components

Sanity Live + Cache Components

Wires
next-sanity
into a Next.js 16+ app with
cacheComponents: true
. Data is fetched with
sanityFetch
(which calls
cacheTag
/
cacheLife
internally), and
<SanityLive>
in the root layout revalidates cached content over an EventSource connection to Sanity Content Lake. Visual Editing and Presentation Tool are fully supported when draft mode is enabled.
Read the relevant guide in
node_modules/next/dist/docs/
(when available) before writing code. If a guide conflicts with this skill, follow this skill.
This skill assumes familiarity with the
next-cache-components
skill — it covers
'use cache'
,
cacheLife
,
cacheTag
, and the cookies/headers/params rule. The only Sanity-relevant exception:
await draftMode()
is allowed inside
'use cache'
(Next.js bypasses caching when draft mode is enabled — see the
use cache
reference
).
next-sanity
接入启用
cacheComponents: true
的Next.js 16+应用。通过
sanityFetch
(内部调用
cacheTag
/
cacheLife
)获取数据,根布局中的
<SanityLive>
通过与Sanity Content Lake的EventSource连接重新验证缓存内容。启用草稿模式后,Visual Editing和Presentation Tool将获得全面支持。
在编写代码前,请先阅读
node_modules/next/dist/docs/
中的相关指南(若可用)。若指南与本技能内容冲突,请以本技能为准。
本技能假定使用者已熟悉
next-cache-components
技能——涵盖
'use cache'
cacheLife
cacheTag
以及cookies/headers/params规则。与Sanity相关的唯一例外:
await draftMode()
允许在
'use cache'
内部使用(Next.js会在启用草稿模式时绕过缓存——详见
use cache
参考文档
)。

Prerequisites

前提条件

  • Next.js 16.2+ installed in the project (check
    package.json
    or run
    pnpm list next
    /
    npm ls next
    — don't use
    pnpm view next version
    , that reports the registry's latest, not what's installed).
  • AGENTS.md
    exists, or follow the guide.
  • These environment variables are set:
    • NEXT_PUBLIC_SANITY_PROJECT_ID
    • NEXT_PUBLIC_SANITY_DATASET
    • SANITY_API_READ_TOKEN
  • Embedded Sanity Studio configuration (
    sanity.config.ts
    ,
    sanity.cli.ts
    , anything under
    sanity/
    ) needs no changes — this skill only touches the Next.js app surface.
  • 项目中已安装Next.js 16.2+(查看
    package.json
    或运行
    pnpm list next
    /
    npm ls next
    — 不要使用
    pnpm view next version
    ,该命令会显示注册表中的最新版本,而非已安装版本)。
  • 存在
    AGENTS.md
    ,或遵循相关指南
  • 已设置以下环境变量:
    • NEXT_PUBLIC_SANITY_PROJECT_ID
    • NEXT_PUBLIC_SANITY_DATASET
    • SANITY_API_READ_TOKEN
  • 嵌入式Sanity Studio配置(
    sanity.config.ts
    sanity.cli.ts
    sanity/
    下的所有文件)无需修改——本技能仅涉及Next.js应用层面的改动。

Reference files

参考文件

FileWhen to read
reference/live-helpers.mdFull
client.ts
/
live.ts
,
sanityFetch*
and
getDynamicFetchOptions
details
reference/three-layer-pattern.mdThe Page → Dynamic → Cached pattern for
page.tsx
, including the
searchParams
variant
reference/layouts.mdNon-blocking data fetching inside
layout.tsx
with a shared
'use cache'
helper
reference/dynamic-segments.mdHigh-performance
[slug]
routes:
loading.tsx
+ partial
generateStaticParams
, or non-blocking dynamic
params
in a layout

文件阅读时机
reference/live-helpers.md完整的
client.ts
/
live.ts
sanityFetch*
getDynamicFetchOptions
细节说明
reference/three-layer-pattern.md
page.tsx
的页面→动态→缓存三层模式,包括
searchParams
变体
reference/layouts.md
layout.tsx
中使用共享
'use cache'
助手实现非阻塞数据获取
reference/dynamic-segments.md高性能
[slug]
路由:
loading.tsx
+ 部分
generateStaticParams
,或在布局中使用非阻塞动态
params

1. Install
next-sanity@^13

1. 安装
next-sanity@^13

bash
npm install next-sanity@^13 --save-exact
bash
npm install next-sanity@^13 --save-exact

Migrating an existing Sanity Live setup

迁移现有Sanity Live配置

If the app is already using
defineLive
, this skill is a refactor, not a rewrite. The 5-step sequence below still applies, but watch for these specific differences:
  • Don't overwrite
    client.ts
    or
    live.ts
    if they exist. Append missing options. Preserve any existing
    token
    and
    stega.*
    settings — see reference/live-helpers.md.
  • Search the codebase for hardcoded
    perspective: 'published'
    and
    stega: false
    in
    sanityFetch
    callsites and refactor them to source
    perspective
    /
    stega
    via
    getDynamicFetchOptions
    and the three-layer pattern.
  • Search for
    sanityFetch
    calls inside
    generateStaticParams
    → swap for
    sanityFetchStaticParams
    .
  • Search for
    sanityFetch
    calls inside
    generateMetadata
    /
    sitemap.ts
    /
    opengraph-image.tsx
    / etc.
    → swap for
    sanityFetchMetadata
    .
  • Search for
    sanityFetch
    calls directly inside a
    'use server'
    function
    → split into a separate
    'use cache'
    helper.
  • Verify there is exactly one
    <SanityLive>
    and one
    <VisualEditing>
    in the tree.
    Multiple renders are undefined behavior.
The "Anti-patterns to grep for" section at the bottom of this file lists the search patterns.

若应用已在使用
defineLive
,本技能属于重构而非重写。以下5步流程仍然适用,但需注意以下差异:
  • client.ts
    live.ts
    已存在,不要覆盖。追加缺失的配置项。保留现有
    token
    stega.*
    设置——详见reference/live-helpers.md
  • 在代码库中搜索硬编码的
    perspective: 'published'
    stega: false
    ,并重构为通过
    getDynamicFetchOptions
    和三层模式获取
    perspective
    /
    stega
  • 搜索
    generateStaticParams
    中的
    sanityFetch
    调用→替换为
    sanityFetchStaticParams
  • 搜索
    generateMetadata
    /
    sitemap.ts
    /
    opengraph-image.tsx
    等文件中的
    sanityFetch
    调用→替换为
    sanityFetchMetadata
  • 搜索直接在
    'use server'
    函数内的
    sanityFetch
    调用→拆分为独立的
    'use cache'
    助手。
  • 确保树中仅存在一个
    <SanityLive>
    和一个
    <VisualEditing>
    。多次渲染会导致未定义行为。
本文底部的“需要排查的反模式”部分列出了具体搜索模式。

2. Configure
next.config.ts

2. 配置
next.config.ts

Enable
cacheComponents
and set
cacheLife.default
to
sanity
so default revalidation is 1 year (instead of 15 minutes).
sanityFetch
is optimized for on-demand revalidation and doesn't need time-based revalidation.
ts
// next.config.ts
import type {NextConfig} from 'next'
import {sanity} from 'next-sanity/live/cache-life'

const nextConfig: NextConfig = {
  cacheComponents: true,
  cacheLife: {default: sanity},
}

export default nextConfig

启用
cacheComponents
并将
cacheLife.default
设置为
sanity
,使默认重新验证周期为1年(而非15分钟)。
sanityFetch
针对按需重新验证优化,无需基于时间的重新验证。
ts
// next.config.ts
import type {NextConfig} from 'next'
import {sanity} from 'next-sanity/live/cache-life'

const nextConfig: NextConfig = {
  cacheComponents: true,
  cacheLife: {default: sanity},
}

export default nextConfig

3. Configure
defineLive
and export helpers

3. 配置
defineLive
并导出助手

Create
src/sanity/lib/client.ts
and
src/sanity/lib/live.ts
. The minimal
defineLive
call:
ts
// src/sanity/lib/live.ts (excerpt)
export const {SanityLive, sanityFetch} = defineLive({
  client,
  serverToken: token,
  browserToken: token,
  strict: true,
})
Full file contents (including
client.ts
,
getDynamicFetchOptions
,
sanityFetchMetadata
,
sanityFetchStaticParams
) and per-helper guidance: reference/live-helpers.md.
The helpers exported from
live.ts
:
HelperUsed in
sanityFetch
'use cache'
components rendered from
page.tsx
/
layout.tsx
sanityFetchMetadata
generateMetadata
,
generateViewport
,
sitemap.ts
,
robots.ts
,
opengraph-image.tsx
, etc.
sanityFetchStaticParams
generateStaticParams
only
getDynamicFetchOptions
Resolving
perspective
/
stega
outside any
'use cache'
boundary
SanityLive
Rendered once in a root layout

创建
src/sanity/lib/client.ts
src/sanity/lib/live.ts
。最小化
defineLive
调用如下:
ts
// src/sanity/lib/live.ts (节选)
export const {SanityLive, sanityFetch} = defineLive({
  client,
  serverToken: token,
  browserToken: token,
  strict: true,
})
完整文件内容(包括
client.ts
getDynamicFetchOptions
sanityFetchMetadata
sanityFetchStaticParams
)及各助手的使用指南:reference/live-helpers.md
live.ts
导出的助手:
助手名称使用场景
sanityFetch
page.tsx
/
layout.tsx
渲染的
'use cache'
组件中使用
sanityFetchMetadata
generateMetadata
generateViewport
sitemap.ts
robots.ts
opengraph-image.tsx
等文件中使用
sanityFetchStaticParams
仅在
generateStaticParams
中使用
getDynamicFetchOptions
'use cache'
边界外解析
perspective
/
stega
SanityLive
在根布局中渲染一次

4. Render
<SanityLive>
in a root layout

4. 在根布局中渲染
<SanityLive>

<SanityLive>
and
<VisualEditing>
both belong in a
layout.tsx
, never a
page.tsx
. Both must be rendered at most once across the whole tree — duplicate renders are undefined behavior.
  • includeDrafts
    is required when
    defineLive
    is configured with
    strict: true
    (the recommended setup). TypeScript will surface the error if it's missing; pass
    includeDrafts={isDraftMode}
    so live revalidation includes drafts only in draft mode.
  • Preserve any existing optional callback props on
    <SanityLive>
    when migrating:
    onError
    ,
    onWelcome
    ,
    onReconnect
    . They are commonly wired to a toast/notification helper and silently dropping them regresses UX.
tsx
// src/app/layout.tsx
import {SanityLive} from '@/sanity/lib/live'
import {VisualEditing} from 'next-sanity/visual-editing'
import {draftMode} from 'next/headers'

export default async function RootLayout({children}: LayoutProps<'/'>) {
  const {isEnabled: isDraftMode} = await draftMode()
  return (
    <html lang="en">
      <body>
        {children}
        <SanityLive includeDrafts={isDraftMode} />
        {isDraftMode && <VisualEditing />}
      </body>
    </html>
  )
}
<SanityLive>
<VisualEditing>
都应放在
layout.tsx
中,绝不能放在
page.tsx
中。两者在整个组件树中最多只能渲染一次——重复渲染会导致未定义行为。
  • defineLive
    配置为
    strict: true
    (推荐配置)时,
    includeDrafts
    必填项。若缺失,TypeScript会提示错误;传入
    includeDrafts={isDraftMode}
    ,使实时重新验证仅在草稿模式下包含草稿内容。
  • 迁移时保留
    <SanityLive>
    上已有的可选回调属性:
    onError
    onWelcome
    onReconnect
    。这些属性通常与提示/通知助手关联,若删除会导致用户体验退化。
tsx
// src/app/layout.tsx
import {SanityLive} from '@/sanity/lib/live'
import {VisualEditing} from 'next-sanity/visual-editing'
import {draftMode} from 'next/headers'

export default async function RootLayout({children}: LayoutProps<'/'>) {
  const {isEnabled: isDraftMode} = await draftMode()
  return (
    <html lang="en">
      <body>
        {children}
        <SanityLive includeDrafts={isDraftMode} />
        {isDraftMode && <VisualEditing />}
      </body>
    </html>
  )
}

With an embedded Sanity Studio

搭配嵌入式Sanity Studio

If a route mounts
NextStudio
from
next-sanity/studio
(e.g.
app/studio/[[...index]]/page.tsx
),
<SanityLive>
must live in a layout the embedded studio doesn't share. Use route groups: put
<SanityLive>
in
src/app/(website)/layout.tsx
and keep the rest of the app under
src/app/(website)
.

若某个路由挂载了
next-sanity/studio
中的
NextStudio
(例如
app/studio/[[...index]]/page.tsx
),
<SanityLive>
必须放在嵌入式工作室未共享的布局中。使用路由组:将
<SanityLive>
放在
src/app/(website)/layout.tsx
中,其余应用内容放在
src/app/(website)
下。

5. Apply the three-layer pattern to pages and layouts

5. 为页面和布局应用三层模式

Every route that should be statically prerendered uses the same shape:
text
Page/Layout (Layer 1: draftMode branch)
  ├── NOT draft mode → <CachedX perspective="published" stega={false} />  (no Suspense)
  └── draft mode → <Suspense fallback={...}>
                      <DynamicX params={params} />  (Layer 2: awaits dynamic APIs)
                        └── <CachedX perspective={p} stega={s} />  (Layer 3: 'use cache')
Critical rule: Only Layer 3 carries
'use cache'
. The top-level
Page
/
Layout
must not have
'use cache'
— it awaits
params
,
searchParams
, or
cookies()
(via
getDynamicFetchOptions
), and those dynamic APIs are forbidden inside
'use cache'
. Layer 3 carrying
'use cache'
is enough for the whole route to prerender into the static shell. Adding
'use cache'
to the top-level function is the most common failure mode — TypeScript and the runtime will both complain.
Pick the right reference for the file you're editing:
  • page.tsx
    with static or
    generateStaticParams
    -backed params → reference/three-layer-pattern.md.
  • page.tsx
    that uses
    searchParams
    or other dynamic APIs → the
    searchParams
    variant in reference/three-layer-pattern.md.
  • layout.tsx
    that fetches its own data → reference/layouts.md.
  • Dynamic
    [slug]
    route
    that needs the
    loading.tsx
    + partial
    generateStaticParams
    optimization, or a layout that needs non-blocking
    params
    reference/dynamic-segments.md.

每个需要静态预渲染的路由都遵循以下结构:
text
页面/布局(第一层:草稿模式分支)
  ├── 非草稿模式 → <CachedX perspective="published" stega={false} /> (无Suspense)
  └── 草稿模式 → <Suspense fallback={...}>
                      <DynamicX params={params} /> (第二层:等待动态API)
                        └── <CachedX perspective={p} stega={s} /> (第三层:'use cache')
关键规则:仅第三层使用
'use cache'
。顶层页面/布局不能使用
'use cache'
——它需要等待
params
searchParams
cookies()
(通过
getDynamicFetchOptions
),而这些动态API在
'use cache'
内部是被禁止的。第三层使用
'use cache'
已足够让整个路由预渲染为静态外壳。在顶层函数中添加
'use cache'
是最常见的错误模式——TypeScript和运行时都会报错。
根据正在编辑的文件选择对应的参考文档:
  • 带有静态参数或
    generateStaticParams
    支持参数的**
    page.tsx
    ** → reference/three-layer-pattern.md
  • 使用
    searchParams
    或其他动态API的**
    page.tsx
    ** → reference/three-layer-pattern.md中的
    searchParams
    变体。
  • 自行获取数据的**
    layout.tsx
    ** → reference/layouts.md
  • 需要
    loading.tsx
    + 部分
    generateStaticParams
    优化的动态
    [slug]
    路由
    ,或需要非阻塞
    params
    的布局 → reference/dynamic-segments.md

Anti-patterns to grep for

需要排查的反模式

When auditing an app, search for these and refactor:
  • perspective: 'published'
    and
    stega: false
    hardcoded together in a
    sanityFetch
    call → use the three-layer pattern, source
    perspective
    /
    stega
    via
    getDynamicFetchOptions
    .
  • sanityFetch(
    directly inside a function whose body begins with
    'use server'
    → split into a separate
    'use cache'
    helper.
  • sanityFetch(
    inside
    generateStaticParams
    → swap for
    sanityFetchStaticParams
    .
  • sanityFetch(
    inside
    generateMetadata
    /
    generateViewport
    /
    sitemap.ts
    /
    robots.ts
    /
    opengraph-image.tsx
    etc. → swap for
    sanityFetchMetadata
    and resolve
    perspective
    via
    getDynamicFetchOptions
    .
  • await draftMode()
    immediately followed by
    await getDynamicFetchOptions()
    at the top of a
    page.tsx
    or
    layout.tsx
    without a sibling
    loading.tsx
    → move those dynamic-API calls into a child component wrapped in
    <Suspense>
    so the static shell can prerender.
  • More than one
    <SanityLive>
    or
    <VisualEditing>
    rendered in the tree → consolidate to a single render in the right layout.
审核应用时,搜索以下内容并进行重构:
  • sanityFetch
    调用中同时硬编码
    perspective: 'published'
    stega: false
    → 使用三层模式,通过
    getDynamicFetchOptions
    获取
    perspective
    /
    stega
  • 直接在以
    'use server'
    开头的函数内调用
    sanityFetch(
    → 拆分为独立的
    'use cache'
    助手。
  • generateStaticParams
    中的
    sanityFetch(
    → 替换为
    sanityFetchStaticParams
  • generateMetadata
    /
    generateViewport
    /
    sitemap.ts
    /
    robots.ts
    /
    opengraph-image.tsx
    等文件中的
    sanityFetch(
    → 替换为
    sanityFetchMetadata
    ,并通过
    getDynamicFetchOptions
    解析
    perspective
  • page.tsx
    layout.tsx
    顶部连续调用
    await draftMode()
    await getDynamicFetchOptions()
    ,且无同级
    loading.tsx
    → 将这些动态API调用移至包裹在
    <Suspense>
    中的子组件,以便静态外壳可以预渲染。
  • 组件树中渲染多个
    <SanityLive>
    <VisualEditing>
    → 合并为在正确布局中单次渲染。