关于 sanity-live-cache-components
面向编码 Agent 的技能,用于将 Sanity Live 接入 Next.js 16+ Cache Components。它涵盖 Sanity 数据获取、草稿模式和实时重新验证,并将通用框架流程交由 Next.js 官方技能处理。
主要功能
- 01配置 Sanity Live 与 Cache Components
- 02提供共享的 Sanity 缓存获取辅助函数
- 03接入草稿模式与 Visual Editing
- 04实时重新验证内容
- 05提供迁移与运行时验证指引
适用场景
- 01在 Next.js 应用中接入 Sanity Live
- 02迁移现有 Sanity 数据获取模式
- 03通过 Presentation Tool 支持草稿预览
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ sanity-io/ next-sanity --skill sanity-live-cache-components安装后在 Claude Code 中输入/sanity-live-cache-components确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
reference/dynamic-segments.mdlayouts.mdlive-helpers.mdthree-layer-pattern.md详细介绍
Sanity Live + Cache Components
将 next-sanity 接入启用了 cacheComponents: true 的 Next.js 16+ 应用。数据通过 sanityFetch 获取,并统一放在共享的 'use cache' 边界(cachedSanity)内;根布局中的 <SanityLive> 会通过连接到 Sanity Content Lake 的 EventSource 连接重新验证缓存内容。启用草稿模式后,完全支持 Visual Editing 和 Presentation Tool。
编写代码前,请先阅读 node_modules/next/dist/docs/ 中的相关指南(如果有)。如果指南与本技能冲突,请遵循本技能。
本技能假设你熟悉 Cache Components 基础知识——'use cache'、cacheLife、cacheTag 以及 cookies/headers/params 规则。这些内容详见 Cache Components 指南(离线文档位于 node_modules/next/dist/docs/)。唯一与 Sanity 相关的例外是:允许在 'use cache' 中使用 await draftMode()(启用草稿模式时,Next.js 会绕过缓存,详见 use cache 参考文档)。
此技能的适用范围
Next.js 为通用框架工作流提供了官方技能(参见 为 AI 编码 Agent 设置项目)。此技能仅涵盖 Sanity 相关内容,其他部分交由这些技能处理。请从 Next.js 仓库安装它们:
npx skills add vercel/next.js --skill next-dev-loop
npx skills add vercel/next.js --skill next-cache-components-adoption
npx skills add vercel/next.js --skill next-cache-components-optimizer
npx skills add vercel/next.js --skill next-partial-prefetching-adoption
当它们的规则适用时——例如阻塞路由排查、<Suspense> 放置、加载 UI 复用、instant() 回归测试、链接预取审查——请遵循这些规则,不要在此处重新推导相关指导。
迁移应用时,推荐按以下顺序进行:
-
next-cache-components-adoption— 启用cacheComponents: true,并逐个路由处理应用,直到构建通过。请告知它将 Sanity 相关部分留给此技能处理:使用 next-cache-components-adoption Skill 在此项目中采用 Cache Components。将草稿模式处理以及所有
sanityFetch/<SanityLive>调用点交由 sanity-live-cache-components skill 处理:让这些路由保持退出状态(export const instant = false),而不是重构 Sanity 数据获取。 -
此技能 — 设置
defineLive和live.ts辅助函数,重构sanityFetch调用点以正确提供perspective/stega,接入<SanityLive>/<VisualEditing>和草稿模式,然后移除 Sanity 延后处理路由上剩余的退出配置。移除时请使用 adoption skill 的逐路由循环和通过标准(开发覆盖层无错误、浏览器验证通过、next build通过)——此技能提供 Sanity 专属修复,循环机制则由 adoption skill 提供。 -
以下一项或两项均可作为可选后续步骤:
next-cache-components-optimizer— 扩展路由的静态外壳,并用@next/playwright的instant()测试进行验证。提示词:“使用 next-cache-components-optimizer Skill,让导航到/<route>的操作即时完成。”next-partial-prefetching-adoption— 启用partialPrefetching并审查<Link prefetch={true}>的用法。提示词:“使用 next-partial-prefetching-adoption Skill 在此项目中采用 Partial Prefetching。”
此技能不会妨碍采用其中任何一项:三层模式可确保发布分支中的路由完全可预渲染,这正是这些技能要扩展和预取的外壳。通过 cachedSanity 缓存的 Sanity 内容也满足运行时预取对“缓存依赖 URL 的内容”的要求。
在整个过程中,请使用 next-dev-loop 在运行时验证更改——编译通过并不能证明静态外壳中最终包含了什么,而哪些内容是通过流式传输得到的。
前提条件
- 项目已安装 Next.js 16.3+(检查
package.json,或运行pnpm list next/npm ls next——不要使用pnpm view next version,它报告的是注册表中的最新版本,而非已安装的版本)。next-sanityv13 支持 Next.js 16,但此技能配合使用的官方技能要求 Next.js 16.3+。 - 存在
AGENTS.md。在 Next.js 16.3+ 中,next dev会自动生成该文件(指引 Agent 查看捆绑的文档);在较旧版本中,请遵循指南。 - 已设置以下环境变量:
NEXT_PUBLIC_SANITY_PROJECT_IDNEXT_PUBLIC_SANITY_DATASETSANITY_API_READ_TOKEN
- 嵌入式 Sanity Studio 配置(
sanity.config.ts、sanity.cli.ts、sanity/下的所有内容)无需更改——此技能只处理 Next.js 应用部分。
参考文件
| 文件 | 阅读时机 |
|---|---|
| reference/live-helpers.md | 查看完整的 client.ts / live.ts、cachedSanity、sanityFetch* 和 getDynamicFetchOptions 详情 |
| reference/three-layer-pattern.md | 了解 page.tsx 的 Page → Dynamic → Cached 模式,包括 searchParams 变体 |
| reference/layouts.md | 了解在 layout.tsx 中进行非阻塞数据获取 |
| reference/dynamic-segments.md | 高性能 [slug] 路由:loading.tsx + 部分 generateStaticParams,或在布局中使用非阻塞动态 params |
1. 安装 next-sanity@^13
npm install next-sanity@^13 --save-exact
迁移现有的 Sanity Live 设置
如果应用已经在使用 defineLive,这项工作是重构,而不是重写。下面的 5 步流程仍然适用,但请留意以下具体差异:
- 如果
client.ts或live.ts已存在,不要覆盖它们。 补充缺少的选项。保留现有的token和stega.*设置——请参阅 reference/live-helpers.md。 - 在代码库中搜索
sanityFetch调用处硬编码的perspective: 'published'和stega: false,并重构为通过getDynamicFetchOptions和三层模式获取perspective/stega。 - 搜索
generateStaticParams中的sanityFetch调用 → 替换为cachedSanityStaticParams。 - 搜索
generateMetadata/sitemap.ts/opengraph-image.tsx/ 等文件中的sanityFetch调用 → 替换为cachedSanityMetadata。 - 搜索直接在
'use server'函数中的sanityFetch调用 → 替换为cachedSanity。 - 确认树中恰好只有一个
<SanityLive>和一个<VisualEditing>。 多次渲染会产生未定义行为。
本文件底部的“要用 grep 搜索的反模式”一节列出了搜索模式。
2. 配置 next.config.ts
启用 cacheComponents,并将 cacheLife.default 设为 sanity,这样默认重新验证间隔为 1 年(而不是 15 分钟)。sanityFetch 针对按需重新验证进行了优化,不需要基于时间的重新验证。
// 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. 配置 defineLive 并导出辅助函数
创建 src/sanity/lib/client.ts 和 src/sanity/lib/live.ts。live.ts 的核心内容:
// src/sanity/lib/live.ts (excerpt)
export const {SanityLive, sanityFetch} = defineLive({
client,
serverToken: token,
browserToken: token,
strict: true,
})
// The app's one shared 'use cache' boundary. `sanityFetch` calls
// `cacheTag`/`cacheLife` internally but doesn't create the boundary —
// this wrapper provides it once, so callers don't add their own.
export const cachedSanity: StrictDefinedFetchType = async (options) => {
'use cache'
return sanityFetch(options)
}
完整文件内容(包括 client.ts、getDynamicFetchOptions、cachedSanityMetadata、cachedSanityStaticParams)以及各辅助函数的使用说明,请参阅:reference/live-helpers.md。
从 live.ts 导出的辅助函数:
| 辅助函数 | 用途 |
|---|---|
cachedSanity | 在服务端任何位置获取内容时的默认选择:页面、布局、组件、服务端操作 |
sanityFetch | 仅用于带有自身 'use cache' 的组件中(也会缓存渲染后的 JSX) |
cachedSanityMetadata | generateMetadata、generateViewport、sitemap.ts、robots.ts、opengraph-image.tsx 等 |
cachedSanityStaticParams | 仅用于 generateStaticParams |
getDynamicFetchOptions | 在任何 'use cache' 边界之外解析 perspective/stega |
SanityLive | 在根布局中渲染一次 |
4. 在根布局中渲染 <SanityLive>
<SanityLive> 和 <VisualEditing> 都应放在 layout.tsx 中,绝不能放在 page.tsx 中。两者在整个树中都最多只能渲染一次——重复渲染的行为未定义。
- 当
defineLive配置为strict: true(推荐设置)时,必须设置includeDrafts。如果缺少此项,TypeScript 会提示错误;传入includeDrafts={isDraftMode},这样只有在草稿模式下,实时重新验证才会包含草稿。 - 迁移时,请保留
<SanityLive>上已有的可选回调属性:onError、onWelcome、onReconnect。这些回调通常会连接到 Toast/通知辅助函数;不加说明地删除它们会导致用户体验退化。
// 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>
)
}
使用嵌入式 Sanity Studio
如果某个路由挂载了来自 next-sanity/studio 的 NextStudio(例如 app/studio/[[...index]]/page.tsx),<SanityLive> 必须放在嵌入式 Studio 不共用的布局中。使用路由组:将 <SanityLive> 放在 src/app/(website)/layout.tsx 中,并将应用的其余部分保留在 src/app/(website) 下。
5. 将三层模式应用于页面和布局
所有需要静态预渲染的路由都采用相同的结构:
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: fetches via cachedSanity)
关键规则:
- 缓存边界位于
live.ts中(cachedSanity),因此路由文件通常不需要添加'use cache'指令。尤其是顶层的Page/Layout不能添加'use cache'——它会等待params、searchParams或cookies()(通过getDynamicFetchOptions),而这些动态 API 禁止在'use cache'中使用。给顶层函数添加'use cache'是最常见的错误,TypeScript 和运行时都会报错。 - 第 3 层等待
cachedSanity,就足以让整个路由预渲染到静态外壳中;在已发布的分支中不需要<Suspense>。perspective和stega会自动成为包装器缓存键的一部分,因此已发布内容和草稿内容永远不会共用缓存条目。 - 只有第 2 层(在
<Suspense>内渲染,且仅限草稿模式)会使用动态 API。
根据要编辑的文件选择相应参考:
- 使用静态参数或由
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。
验证 Sanity 使用情况
每次重构后都使用 next-dev-loop;循环机制和成功标准详见官方技能。需要确认的 Sanity 特定事项:
- 已发布分支:路由完全预渲染(在构建的路由表中显示
◐或○),且内容渲染时不会闪现<Suspense>回退内容。 - 草稿模式:启用后会流式传输草稿内容,显示
<VisualEditing>覆盖层,且在 Presentation Tool 中切换视角会改变渲染的内容。 - 实时更新:在 Studio 中编辑已发布内容后,路由会重新验证(通过
<SanityLive>),无需重新构建。
需要搜索的反模式
审查应用时,请搜索以下内容并进行重构:
- 在共享组件中的
sanityFetch/cachedSanity调用里,将perspective: 'published'和stega: false同时硬编码 → 使用三层模式,通过getDynamicFetchOptions获取perspective/stega。(第 1 层的非草稿分支传入字面值perspective="published" stega={false}属性是正确模式,并非违规。) - 在函数体以
'use server'开头的函数中直接使用sanityFetch(→ 改用cachedSanity(先通过getDynamicFetchOptions获取perspective/stega)。 - 在
generateStaticParams中使用sanityFetch(→ 改用cachedSanityStaticParams。 - 在
generateMetadata/generateViewport/sitemap.ts/robots.ts/opengraph-image.tsx等文件中使用sanityFetch(→ 改用cachedSanityMetadata,并通过getDynamicFetchOptions获取perspective。 - 在没有自身
'use cache'指令的组件中使用sanityFetch(→ 改用cachedSanity(如果有意缓存渲染后的 JSX,也可以添加该指令)。 - 在
page.tsx或layout.tsx顶部调用await draftMode()后立即调用await getDynamicFetchOptions(),且没有同级的loading.tsx→ 将这些动态 API 调用移入由<Suspense>包裹的子组件,以便静态外壳能够预渲染。 - 在组件树中渲染了多个
<SanityLive>或<VisualEditing>→ 整合为在合适的布局中单独渲染一次。