mcpField
S
sanity-live-cache-components

sanity-live-cache-components

github.com/sanity-io开发957533 次安装5 天前更新
代码仓库

关于 sanity-live-cache-components

面向编码 Agent 的技能,用于将 Sanity Live 接入 Next.js 16+ Cache Components。它涵盖 Sanity 数据获取、草稿模式和实时重新验证,并将通用框架流程交由 Next.js 官方技能处理。

主要功能

  1. 01配置 Sanity Live 与 Cache Components
  2. 02提供共享的 Sanity 缓存获取辅助函数
  3. 03接入草稿模式与 Visual Editing
  4. 04实时重新验证内容
  5. 05提供迁移与运行时验证指引

适用场景

  1. 01在 Next.js 应用中接入 Sanity Live
  2. 02迁移现有 Sanity 数据获取模式
  3. 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.md
layouts.md
live-helpers.md
three-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() 回归测试、链接预取审查——请遵循这些规则,不要在此处重新推导相关指导。

迁移应用时,推荐按以下顺序进行:

  1. 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 数据获取。

  2. 此技能 — 设置 defineLive 和 live.ts 辅助函数,重构 sanityFetch 调用点以正确提供 perspective/stega,接入 <SanityLive>/<VisualEditing> 和草稿模式,然后移除 Sanity 延后处理路由上剩余的退出配置。移除时请使用 adoption skill 的逐路由循环和通过标准(开发覆盖层无错误、浏览器验证通过、next build 通过)——此技能提供 Sanity 专属修复,循环机制则由 adoption skill 提供。

  3. 以下一项或两项均可作为可选后续步骤:

    • 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-sanity v13 支持 Next.js 16,但此技能配合使用的官方技能要求 Next.js 16.3+。
  • 存在 AGENTS.md。在 Next.js 16.3+ 中,next dev 会自动生成该文件(指引 Agent 查看捆绑的文档);在较旧版本中,请遵循指南。
  • 已设置以下环境变量:
    • 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/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)
cachedSanityMetadatagenerateMetadata、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。

根据要编辑的文件选择相应参考:


验证 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> → 整合为在合适的布局中单独渲染一次。

评论

支持 Markdown