[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M7E8CYWDXGHTQGADZ7N90YA3":3,"skill-same:01M7E8CYWDXGHTQGADZ7N90YA3":103},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":19,"linkOnly":20,"markdownContent":21,"markdownBody":22,"frontMatter":23,"refName":75,"commitSha":76,"skillDirectory":77,"forksCount":14,"repositoryHtmlUrl":78,"repositoryHomepageUrl":6,"ownerHtmlUrl":79,"ownerBlogUrl":80,"ownerDescription":81,"updatedAt":82,"install":83,"resources":96},"01M7E8CYWDXGHTQGADZ7N90YA3","cms",null,"Headless CMS integration guidance — Sanity (native Vercel Marketplace), Contentful, DatoCMS, Storyblok, and Builder.io. Covers studio setup, content modeling, preview mode, revalidation webhooks, and Visual Editing. Use when building content-driven sites with a headless CMS on Vercel.","wxdwu\u002Fnew-api","wxdwu","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F80968397?v=4","01M7E6AEMB59N90BKXD77EC83D","Xudong Wu","01M7E6AEMY2BXCDVZ9MJJHFQ4C",0,"AGPL-3.0","https:\u002F\u002Fgithub.com\u002Fwxdwu\u002Fnew-api\u002Fblob\u002Fmain\u002Fplugins\u002F.tmp\u002Fplugins\u002Fplugins\u002Fvercel\u002Fskills\u002Fcms\u002FSKILL.md",[],[],"2026-11-15 14:19:17",false,"---\nname: cms\ndescription: Headless CMS integration guidance — Sanity (native Vercel Marketplace), Contentful, DatoCMS, Storyblok, and Builder.io. Covers studio setup, content modeling, preview mode, revalidation webhooks, and Visual Editing. Use when building content-driven sites with a headless CMS on Vercel.\nmetadata:\n  priority: 4\n  docs:\n    - \"https:\u002F\u002Fvercel.com\u002Fdocs\u002Fsolutions\u002Fcms\"\n    - \"https:\u002F\u002Fnextjs.org\u002Fdocs\u002Fapp\u002Fbuilding-your-application\u002Fdata-fetching\"\n  sitemap: \"https:\u002F\u002Fvercel.com\u002Fsitemap\u002Fdocs.xml\"\n  pathPatterns:\n    - 'sanity.config.*'\n    - 'sanity.cli.*'\n    - 'sanity\u002F**'\n    - 'studio\u002F**'\n    - 'schemas\u002F*.ts'\n    - 'schemas\u002F*.tsx'\n    - 'src\u002Fsanity\u002F**'\n    - 'lib\u002Fsanity.*'\n    - 'src\u002Flib\u002Fsanity.*'\n    - 'lib\u002Fcontentful.*'\n    - 'src\u002Flib\u002Fcontentful.*'\n    - 'app\u002Fapi\u002Fdraft\u002F**'\n    - 'src\u002Fapp\u002Fapi\u002Fdraft\u002F**'\n    - 'app\u002Fapi\u002Frevalidate\u002F**'\n    - 'src\u002Fapp\u002Fapi\u002Frevalidate\u002F**'\n    - 'app\u002Fstudio\u002F**'\n    - 'src\u002Fapp\u002Fstudio\u002F**'\n  bashPatterns:\n    - '\\bnpm\\s+(install|i|add)\\s+[^\\n]*@sanity\u002Fclient\\b'\n    - '\\bpnpm\\s+(install|i|add)\\s+[^\\n]*@sanity\u002Fclient\\b'\n    - '\\bbun\\s+(install|i|add)\\s+[^\\n]*@sanity\u002Fclient\\b'\n    - '\\byarn\\s+add\\s+[^\\n]*@sanity\u002Fclient\\b'\n    - '\\bnpm\\s+(install|i|add)\\s+[^\\n]*\\bnext-sanity\\b'\n    - '\\bpnpm\\s+(install|i|add)\\s+[^\\n]*\\bnext-sanity\\b'\n    - '\\bbun\\s+(install|i|add)\\s+[^\\n]*\\bnext-sanity\\b'\n    - '\\byarn\\s+add\\s+[^\\n]*\\bnext-sanity\\b'\n    - '\\bnpm\\s+(install|i|add)\\s+[^\\n]*\\bcontentful\\b'\n    - '\\bpnpm\\s+(install|i|add)\\s+[^\\n]*\\bcontentful\\b'\n    - '\\bbun\\s+(install|i|add)\\s+[^\\n]*\\bcontentful\\b'\n    - '\\byarn\\s+add\\s+[^\\n]*\\bcontentful\\b'\n    - '\\bnpm\\s+(install|i|add)\\s+[^\\n]*@builder\\.io\u002Fsdk\\b'\n    - '\\bpnpm\\s+(install|i|add)\\s+[^\\n]*@builder\\.io\u002Fsdk\\b'\n    - '\\bbun\\s+(install|i|add)\\s+[^\\n]*@builder\\.io\u002Fsdk\\b'\n    - '\\byarn\\s+add\\s+[^\\n]*@builder\\.io\u002Fsdk\\b'\n    - '\\bnpm\\s+(install|i|add)\\s+[^\\n]*@storyblok\u002F\\b'\n    - '\\bpnpm\\s+(install|i|add)\\s+[^\\n]*@storyblok\u002F\\b'\n    - '\\bbun\\s+(install|i|add)\\s+[^\\n]*@storyblok\u002F\\b'\n    - '\\byarn\\s+add\\s+[^\\n]*@storyblok\u002F\\b'\n    - '\\bnpm\\s+(install|i|add)\\s+[^\\n]*datocms-client\\b'\n    - '\\bpnpm\\s+(install|i|add)\\s+[^\\n]*datocms-client\\b'\n    - '\\bbun\\s+(install|i|add)\\s+[^\\n]*datocms-client\\b'\n    - '\\byarn\\s+add\\s+[^\\n]*datocms-client\\b'\n    - '\\bnpx\\s+create-sanity\\b'\n    - '\\bnpx\\s+sanity\\s+init\\b'\n---\n\n# Headless CMS Integration\n\nYou are an expert in integrating headless CMS platforms with Vercel-deployed applications — covering Sanity (native Vercel Marketplace), Contentful, DatoCMS, Storyblok, and Builder.io.\n\n## Sanity (Native Vercel Marketplace Integration)\n\nSanity is the primary CMS integration on the Vercel Marketplace with first-class Visual Editing support.\n\n### Install via Marketplace\n\n```bash\n# Install Sanity from Vercel Marketplace (auto-provisions env vars)\nvercel integration add sanity\n```\n\nAuto-provisioned environment variables:\n- `SANITY_PROJECT_ID` — Sanity project identifier\n- `SANITY_DATASET` — dataset name (usually `production`)\n- `SANITY_API_TOKEN` — read\u002Fwrite API token\n- `NEXT_PUBLIC_SANITY_PROJECT_ID` — client-side project ID\n- `NEXT_PUBLIC_SANITY_DATASET` — client-side dataset name\n\n### SDK Setup\n\n```bash\n# Install Sanity packages for Next.js\nnpm install next-sanity @sanity\u002Fclient @sanity\u002Fimage-url\n\n# For embedded studio (optional)\nnpm install sanity @sanity\u002Fvision\n```\n\n### Client Configuration\n\n```ts\n\u002F\u002F lib\u002Fsanity.ts\nimport { createClient } from \"next-sanity\";\n\nexport const client = createClient({\n  projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!,\n  dataset: process.env.NEXT_PUBLIC_SANITY_DATASET!,\n  apiVersion: \"2026-03-01\",\n  useCdn: true,\n});\n```\n\n### Content Schema\n\n```ts\n\u002F\u002F schemas\u002Fpost.ts\nimport { defineType, defineField } from \"sanity\";\n\nexport const post = defineType({\n  name: \"post\",\n  title: \"Post\",\n  type: \"document\",\n  fields: [\n    defineField({ name: \"title\", type: \"string\" }),\n    defineField({ name: \"slug\", type: \"slug\", options: { source: \"title\" } }),\n    defineField({ name: \"body\", type: \"array\", of: [{ type: \"block\" }] }),\n    defineField({ name: \"mainImage\", type: \"image\", options: { hotspot: true } }),\n    defineField({ name: \"publishedAt\", type: \"datetime\" }),\n  ],\n});\n```\n\n### Embedded Studio (App Router)\n\n```ts\n\u002F\u002F app\u002Fstudio\u002F[[...tool]]\u002Fpage.tsx\n\"use client\";\nimport { NextStudio } from \"next-sanity\u002Fstudio\";\nimport config from \"@\u002Fsanity.config\";\n\nexport default function StudioPage() {\n  return \u003CNextStudio config={config} \u002F>;\n}\n```\n\n```ts\n\u002F\u002F sanity.config.ts\nimport { defineConfig } from \"sanity\";\nimport { structureTool } from \"sanity\u002Fstructure\";\nimport { visionTool } from \"@sanity\u002Fvision\";\nimport { post } from \".\u002Fschemas\u002Fpost\";\n\nexport default defineConfig({\n  name: \"default\",\n  title: \"My Studio\",\n  projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!,\n  dataset: process.env.NEXT_PUBLIC_SANITY_DATASET!,\n  plugins: [structureTool(), visionTool()],\n  schema: { types: [post] },\n});\n```\n\n### Live Content with `defineLive()` (next-sanity v12)\n\nUse `defineLive()` for automatic real-time content updates without manual revalidation. In next-sanity v11+, `defineLive` must be imported from the `next-sanity\u002Flive` subpath:\n\n```ts\n\u002F\u002F lib\u002Fsanity.ts\nimport { createClient } from \"next-sanity\";\nimport { defineLive } from \"next-sanity\u002Flive\";\n\nconst client = createClient({\n  projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!,\n  dataset: process.env.NEXT_PUBLIC_SANITY_DATASET!,\n  apiVersion: \"2026-03-01\",\n  useCdn: true,\n});\n\nexport const { sanityFetch, SanityLive } = defineLive({\n  client,\n  \u002F\u002F Required for draft content in Visual Editing — use a Viewer role token\n  serverToken: process.env.SANITY_API_TOKEN,\n  \u002F\u002F Optional but recommended for faster live preview\n  browserToken: process.env.SANITY_BROWSER_TOKEN,\n});\n```\n\n```tsx\n\u002F\u002F app\u002Fpage.tsx\nimport { sanityFetch, SanityLive } from \"@\u002Flib\u002Fsanity\";\n\nexport default async function Page() {\n  const { data: posts } = await sanityFetch({ query: `*[_type == \"post\"]` });\n  return (\n    \u003C>\n      {posts.map((post) => \u003Cdiv key={post._id}>{post.title}\u003C\u002Fdiv>)}\n      \u003CSanityLive \u002F>\n    \u003C\u002F>\n  );\n}\n```\n\n> **Breaking change in v12**: `defineLive({fetchOptions: {revalidate}})` has been removed. `defineLive({stega})` is deprecated.\n\n### Visual Editing (Presentation Mode)\n\nSanity Visual Editing lets content editors click-to-edit content directly on the live site preview. Requires Sanity Studio v5+ (React 19.2) and `@sanity\u002Fvisual-editing` v5+.\n\n```bash\nnpm install @sanity\u002Fvisual-editing\n```\n\nIn next-sanity v11+, `VisualEditing` must be imported from the `next-sanity\u002Fvisual-editing` subpath:\n\n```ts\n\u002F\u002F app\u002Flayout.tsx\nimport { VisualEditing } from \"next-sanity\u002Fvisual-editing\";\nimport { draftMode } from \"next\u002Fheaders\";\n\nexport default async function RootLayout({ children }: { children: React.ReactNode }) {\n  const { isEnabled } = await draftMode();\n  return (\n    \u003Chtml>\n      \u003Cbody>\n        {children}\n        {isEnabled && \u003CVisualEditing \u002F>}\n      \u003C\u002Fbody>\n    \u003C\u002Fhtml>\n  );\n}\n```\n\n### On-Demand Revalidation Webhook\n\n```ts\n\u002F\u002F app\u002Fapi\u002Frevalidate\u002Froute.ts\nimport { revalidateTag } from \"next\u002Fcache\";\nimport { parseBody } from \"next-sanity\u002Fwebhook\";\n\nexport async function POST(req: Request) {\n  const { isValidSignature, body } = await parseBody\u003C{\n    _type: string;\n    slug?: { current?: string };\n  }>(req, process.env.SANITY_REVALIDATE_SECRET);\n\n  if (!isValidSignature) {\n    return Response.json({ message: \"Invalid signature\" }, { status: 401 });\n  }\n\n  if (body?._type) {\n    revalidateTag(body._type);\n  }\n\n  return Response.json({ revalidated: true, now: Date.now() });\n}\n```\n\nConfigure the webhook in Sanity at **Settings → API → Webhooks** pointing to `https:\u002F\u002Fyour-site.vercel.app\u002Fapi\u002Frevalidate`.\n\n## Contentful\n\n```bash\nnpm install contentful\n```\n\n```ts\n\u002F\u002F lib\u002Fcontentful.ts\nimport { createClient } from \"contentful\";\n\nexport const contentful = createClient({\n  space: process.env.CONTENTFUL_SPACE_ID!,\n  accessToken: process.env.CONTENTFUL_ACCESS_TOKEN!,\n});\n```\n\n### Fetching Entries\n\n```ts\n\u002F\u002F app\u002Fpage.tsx\nimport { contentful } from \"@\u002Flib\u002Fcontentful\";\n\nexport default async function Page() {\n  const entries = await contentful.getEntries({ content_type: \"blogPost\" });\n  return (\n    \u003Cul>\n      {entries.items.map((entry) => (\n        \u003Cli key={entry.sys.id}>{entry.fields.title as string}\u003C\u002Fli>\n      ))}\n    \u003C\u002Ful>\n  );\n}\n```\n\n## Draft Mode (Preview)\n\nAll CMS integrations should use Next.js Draft Mode for preview:\n\n```ts\n\u002F\u002F app\u002Fapi\u002Fdraft\u002Froute.ts\nimport { draftMode } from \"next\u002Fheaders\";\n\nexport async function GET(req: Request) {\n  const { searchParams } = new URL(req.url);\n  const secret = searchParams.get(\"secret\");\n\n  if (secret !== process.env.DRAFT_SECRET) {\n    return Response.json({ message: \"Invalid token\" }, { status: 401 });\n  }\n\n  const draft = await draftMode();\n  draft.enable();\n\n  const slug = searchParams.get(\"slug\") ?? \"\u002F\";\n  return Response.redirect(new URL(slug, req.url));\n}\n```\n\n## Environment Variables\n\n| Variable | Scope | CMS | Description |\n|----------|-------|-----|-------------|\n| `SANITY_PROJECT_ID` \u002F `NEXT_PUBLIC_SANITY_PROJECT_ID` | Server \u002F Client | Sanity | Project identifier |\n| `SANITY_DATASET` \u002F `NEXT_PUBLIC_SANITY_DATASET` | Server \u002F Client | Sanity | Dataset name |\n| `SANITY_API_TOKEN` | Server | Sanity | Read\u002Fwrite token |\n| `SANITY_REVALIDATE_SECRET` | Server | Sanity | Webhook secret for revalidation |\n| `CONTENTFUL_SPACE_ID` | Server | Contentful | Space identifier |\n| `CONTENTFUL_ACCESS_TOKEN` | Server | Contentful | Delivery API token |\n| `CONTENTFUL_PREVIEW_TOKEN` | Server | Contentful | Preview API token |\n| `DATOCMS_API_TOKEN` | Server | DatoCMS | Read-only API token |\n\n## Cross-References\n\n- **Marketplace install and env var provisioning** → `⤳ skill: marketplace`\n- **On-demand revalidation and caching** → `⤳ skill: runtime-cache`\n- **Draft mode and middleware patterns** → `⤳ skill: routing-middleware`\n- **Environment variable management** → `⤳ skill: env-vars`\n- **Image optimization** → `⤳ skill: nextjs`\n\n## Official Documentation\n\n- [Sanity + Vercel Marketplace](https:\u002F\u002Fvercel.com\u002Fmarketplace\u002Fsanity)\n- [next-sanity Documentation](https:\u002F\u002Fgithub.com\u002Fsanity-io\u002Fnext-sanity) (v12)\n- [next-sanity v11→v12 Migration](https:\u002F\u002Fgithub.com\u002Fsanity-io\u002Fnext-sanity\u002Freleases)\n- [Sanity Visual Editing](https:\u002F\u002Fwww.sanity.io\u002Fdocs\u002Fvisual-editing)\n- [Contentful JavaScript SDK](https:\u002F\u002Fwww.contentful.com\u002Fdevelopers\u002Fdocs\u002Fjavascript\u002F)\n- [Next.js Draft Mode](https:\u002F\u002Fnextjs.org\u002Fdocs\u002Fapp\u002Fbuilding-your-application\u002Fconfiguring\u002Fdraft-mode)\n","# Headless CMS Integration\n\nYou are an expert in integrating headless CMS platforms with Vercel-deployed applications — covering Sanity (native Vercel Marketplace), Contentful, DatoCMS, Storyblok, and Builder.io.\n\n## Sanity (Native Vercel Marketplace Integration)\n\nSanity is the primary CMS integration on the Vercel Marketplace with first-class Visual Editing support.\n\n### Install via Marketplace\n\n```bash\n# Install Sanity from Vercel Marketplace (auto-provisions env vars)\nvercel integration add sanity\n```\n\nAuto-provisioned environment variables:\n- `SANITY_PROJECT_ID` — Sanity project identifier\n- `SANITY_DATASET` — dataset name (usually `production`)\n- `SANITY_API_TOKEN` — read\u002Fwrite API token\n- `NEXT_PUBLIC_SANITY_PROJECT_ID` — client-side project ID\n- `NEXT_PUBLIC_SANITY_DATASET` — client-side dataset name\n\n### SDK Setup\n\n```bash\n# Install Sanity packages for Next.js\nnpm install next-sanity @sanity\u002Fclient @sanity\u002Fimage-url\n\n# For embedded studio (optional)\nnpm install sanity @sanity\u002Fvision\n```\n\n### Client Configuration\n\n```ts\n\u002F\u002F lib\u002Fsanity.ts\nimport { createClient } from \"next-sanity\";\n\nexport const client = createClient({\n  projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!,\n  dataset: process.env.NEXT_PUBLIC_SANITY_DATASET!,\n  apiVersion: \"2026-03-01\",\n  useCdn: true,\n});\n```\n\n### Content Schema\n\n```ts\n\u002F\u002F schemas\u002Fpost.ts\nimport { defineType, defineField } from \"sanity\";\n\nexport const post = defineType({\n  name: \"post\",\n  title: \"Post\",\n  type: \"document\",\n  fields: [\n    defineField({ name: \"title\", type: \"string\" }),\n    defineField({ name: \"slug\", type: \"slug\", options: { source: \"title\" } }),\n    defineField({ name: \"body\", type: \"array\", of: [{ type: \"block\" }] }),\n    defineField({ name: \"mainImage\", type: \"image\", options: { hotspot: true } }),\n    defineField({ name: \"publishedAt\", type: \"datetime\" }),\n  ],\n});\n```\n\n### Embedded Studio (App Router)\n\n```ts\n\u002F\u002F app\u002Fstudio\u002F[[...tool]]\u002Fpage.tsx\n\"use client\";\nimport { NextStudio } from \"next-sanity\u002Fstudio\";\nimport config from \"@\u002Fsanity.config\";\n\nexport default function StudioPage() {\n  return \u003CNextStudio config={config} \u002F>;\n}\n```\n\n```ts\n\u002F\u002F sanity.config.ts\nimport { defineConfig } from \"sanity\";\nimport { structureTool } from \"sanity\u002Fstructure\";\nimport { visionTool } from \"@sanity\u002Fvision\";\nimport { post } from \".\u002Fschemas\u002Fpost\";\n\nexport default defineConfig({\n  name: \"default\",\n  title: \"My Studio\",\n  projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!,\n  dataset: process.env.NEXT_PUBLIC_SANITY_DATASET!,\n  plugins: [structureTool(), visionTool()],\n  schema: { types: [post] },\n});\n```\n\n### Live Content with `defineLive()` (next-sanity v12)\n\nUse `defineLive()` for automatic real-time content updates without manual revalidation. In next-sanity v11+, `defineLive` must be imported from the `next-sanity\u002Flive` subpath:\n\n```ts\n\u002F\u002F lib\u002Fsanity.ts\nimport { createClient } from \"next-sanity\";\nimport { defineLive } from \"next-sanity\u002Flive\";\n\nconst client = createClient({\n  projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!,\n  dataset: process.env.NEXT_PUBLIC_SANITY_DATASET!,\n  apiVersion: \"2026-03-01\",\n  useCdn: true,\n});\n\nexport const { sanityFetch, SanityLive } = defineLive({\n  client,\n  \u002F\u002F Required for draft content in Visual Editing — use a Viewer role token\n  serverToken: process.env.SANITY_API_TOKEN,\n  \u002F\u002F Optional but recommended for faster live preview\n  browserToken: process.env.SANITY_BROWSER_TOKEN,\n});\n```\n\n```tsx\n\u002F\u002F app\u002Fpage.tsx\nimport { sanityFetch, SanityLive } from \"@\u002Flib\u002Fsanity\";\n\nexport default async function Page() {\n  const { data: posts } = await sanityFetch({ query: `*[_type == \"post\"]` });\n  return (\n    \u003C>\n      {posts.map((post) => \u003Cdiv key={post._id}>{post.title}\u003C\u002Fdiv>)}\n      \u003CSanityLive \u002F>\n    \u003C\u002F>\n  );\n}\n```\n\n> **Breaking change in v12**: `defineLive({fetchOptions: {revalidate}})` has been removed. `defineLive({stega})` is deprecated.\n\n### Visual Editing (Presentation Mode)\n\nSanity Visual Editing lets content editors click-to-edit content directly on the live site preview. Requires Sanity Studio v5+ (React 19.2) and `@sanity\u002Fvisual-editing` v5+.\n\n```bash\nnpm install @sanity\u002Fvisual-editing\n```\n\nIn next-sanity v11+, `VisualEditing` must be imported from the `next-sanity\u002Fvisual-editing` subpath:\n\n```ts\n\u002F\u002F app\u002Flayout.tsx\nimport { VisualEditing } from \"next-sanity\u002Fvisual-editing\";\nimport { draftMode } from \"next\u002Fheaders\";\n\nexport default async function RootLayout({ children }: { children: React.ReactNode }) {\n  const { isEnabled } = await draftMode();\n  return (\n    \u003Chtml>\n      \u003Cbody>\n        {children}\n        {isEnabled && \u003CVisualEditing \u002F>}\n      \u003C\u002Fbody>\n    \u003C\u002Fhtml>\n  );\n}\n```\n\n### On-Demand Revalidation Webhook\n\n```ts\n\u002F\u002F app\u002Fapi\u002Frevalidate\u002Froute.ts\nimport { revalidateTag } from \"next\u002Fcache\";\nimport { parseBody } from \"next-sanity\u002Fwebhook\";\n\nexport async function POST(req: Request) {\n  const { isValidSignature, body } = await parseBody\u003C{\n    _type: string;\n    slug?: { current?: string };\n  }>(req, process.env.SANITY_REVALIDATE_SECRET);\n\n  if (!isValidSignature) {\n    return Response.json({ message: \"Invalid signature\" }, { status: 401 });\n  }\n\n  if (body?._type) {\n    revalidateTag(body._type);\n  }\n\n  return Response.json({ revalidated: true, now: Date.now() });\n}\n```\n\nConfigure the webhook in Sanity at **Settings → API → Webhooks** pointing to `https:\u002F\u002Fyour-site.vercel.app\u002Fapi\u002Frevalidate`.\n\n## Contentful\n\n```bash\nnpm install contentful\n```\n\n```ts\n\u002F\u002F lib\u002Fcontentful.ts\nimport { createClient } from \"contentful\";\n\nexport const contentful = createClient({\n  space: process.env.CONTENTFUL_SPACE_ID!,\n  accessToken: process.env.CONTENTFUL_ACCESS_TOKEN!,\n});\n```\n\n### Fetching Entries\n\n```ts\n\u002F\u002F app\u002Fpage.tsx\nimport { contentful } from \"@\u002Flib\u002Fcontentful\";\n\nexport default async function Page() {\n  const entries = await contentful.getEntries({ content_type: \"blogPost\" });\n  return (\n    \u003Cul>\n      {entries.items.map((entry) => (\n        \u003Cli key={entry.sys.id}>{entry.fields.title as string}\u003C\u002Fli>\n      ))}\n    \u003C\u002Ful>\n  );\n}\n```\n\n## Draft Mode (Preview)\n\nAll CMS integrations should use Next.js Draft Mode for preview:\n\n```ts\n\u002F\u002F app\u002Fapi\u002Fdraft\u002Froute.ts\nimport { draftMode } from \"next\u002Fheaders\";\n\nexport async function GET(req: Request) {\n  const { searchParams } = new URL(req.url);\n  const secret = searchParams.get(\"secret\");\n\n  if (secret !== process.env.DRAFT_SECRET) {\n    return Response.json({ message: \"Invalid token\" }, { status: 401 });\n  }\n\n  const draft = await draftMode();\n  draft.enable();\n\n  const slug = searchParams.get(\"slug\") ?? \"\u002F\";\n  return Response.redirect(new URL(slug, req.url));\n}\n```\n\n## Environment Variables\n\n| Variable | Scope | CMS | Description |\n|----------|-------|-----|-------------|\n| `SANITY_PROJECT_ID` \u002F `NEXT_PUBLIC_SANITY_PROJECT_ID` | Server \u002F Client | Sanity | Project identifier |\n| `SANITY_DATASET` \u002F `NEXT_PUBLIC_SANITY_DATASET` | Server \u002F Client | Sanity | Dataset name |\n| `SANITY_API_TOKEN` | Server | Sanity | Read\u002Fwrite token |\n| `SANITY_REVALIDATE_SECRET` | Server | Sanity | Webhook secret for revalidation |\n| `CONTENTFUL_SPACE_ID` | Server | Contentful | Space identifier |\n| `CONTENTFUL_ACCESS_TOKEN` | Server | Contentful | Delivery API token |\n| `CONTENTFUL_PREVIEW_TOKEN` | Server | Contentful | Preview API token |\n| `DATOCMS_API_TOKEN` | Server | DatoCMS | Read-only API token |\n\n## Cross-References\n\n- **Marketplace install and env var provisioning** → `⤳ skill: marketplace`\n- **On-demand revalidation and caching** → `⤳ skill: runtime-cache`\n- **Draft mode and middleware patterns** → `⤳ skill: routing-middleware`\n- **Environment variable management** → `⤳ skill: env-vars`\n- **Image optimization** → `⤳ skill: nextjs`\n\n## Official Documentation\n\n- [Sanity + Vercel Marketplace](https:\u002F\u002Fvercel.com\u002Fmarketplace\u002Fsanity)\n- [next-sanity Documentation](https:\u002F\u002Fgithub.com\u002Fsanity-io\u002Fnext-sanity) (v12)\n- [next-sanity v11→v12 Migration](https:\u002F\u002Fgithub.com\u002Fsanity-io\u002Fnext-sanity\u002Freleases)\n- [Sanity Visual Editing](https:\u002F\u002Fwww.sanity.io\u002Fdocs\u002Fvisual-editing)\n- [Contentful JavaScript SDK](https:\u002F\u002Fwww.contentful.com\u002Fdevelopers\u002Fdocs\u002Fjavascript\u002F)\n- [Next.js Draft Mode](https:\u002F\u002Fnextjs.org\u002Fdocs\u002Fapp\u002Fbuilding-your-application\u002Fconfiguring\u002Fdraft-mode)\n",{"name":5,"metadata":24,"description":7},{"docs":25,"sitemap":28,"priority":29,"bashPatterns":30,"pathPatterns":57},[26,27],"https:\u002F\u002Fvercel.com\u002Fdocs\u002Fsolutions\u002Fcms","https:\u002F\u002Fnextjs.org\u002Fdocs\u002Fapp\u002Fbuilding-your-application\u002Fdata-fetching","https:\u002F\u002Fvercel.com\u002Fsitemap\u002Fdocs.xml",4,[31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56],"\\bnpm\\s+(install|i|add)\\s+[^\\n]*@sanity\u002Fclient\\b","\\bpnpm\\s+(install|i|add)\\s+[^\\n]*@sanity\u002Fclient\\b","\\bbun\\s+(install|i|add)\\s+[^\\n]*@sanity\u002Fclient\\b","\\byarn\\s+add\\s+[^\\n]*@sanity\u002Fclient\\b","\\bnpm\\s+(install|i|add)\\s+[^\\n]*\\bnext-sanity\\b","\\bpnpm\\s+(install|i|add)\\s+[^\\n]*\\bnext-sanity\\b","\\bbun\\s+(install|i|add)\\s+[^\\n]*\\bnext-sanity\\b","\\byarn\\s+add\\s+[^\\n]*\\bnext-sanity\\b","\\bnpm\\s+(install|i|add)\\s+[^\\n]*\\bcontentful\\b","\\bpnpm\\s+(install|i|add)\\s+[^\\n]*\\bcontentful\\b","\\bbun\\s+(install|i|add)\\s+[^\\n]*\\bcontentful\\b","\\byarn\\s+add\\s+[^\\n]*\\bcontentful\\b","\\bnpm\\s+(install|i|add)\\s+[^\\n]*@builder\\.io\u002Fsdk\\b","\\bpnpm\\s+(install|i|add)\\s+[^\\n]*@builder\\.io\u002Fsdk\\b","\\bbun\\s+(install|i|add)\\s+[^\\n]*@builder\\.io\u002Fsdk\\b","\\byarn\\s+add\\s+[^\\n]*@builder\\.io\u002Fsdk\\b","\\bnpm\\s+(install|i|add)\\s+[^\\n]*@storyblok\u002F\\b","\\bpnpm\\s+(install|i|add)\\s+[^\\n]*@storyblok\u002F\\b","\\bbun\\s+(install|i|add)\\s+[^\\n]*@storyblok\u002F\\b","\\byarn\\s+add\\s+[^\\n]*@storyblok\u002F\\b","\\bnpm\\s+(install|i|add)\\s+[^\\n]*datocms-client\\b","\\bpnpm\\s+(install|i|add)\\s+[^\\n]*datocms-client\\b","\\bbun\\s+(install|i|add)\\s+[^\\n]*datocms-client\\b","\\byarn\\s+add\\s+[^\\n]*datocms-client\\b","\\bnpx\\s+create-sanity\\b","\\bnpx\\s+sanity\\s+init\\b",[58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74],"sanity.config.*","sanity.cli.*","sanity\u002F**","studio\u002F**","schemas\u002F*.ts","schemas\u002F*.tsx","src\u002Fsanity\u002F**","lib\u002Fsanity.*","src\u002Flib\u002Fsanity.*","lib\u002Fcontentful.*","src\u002Flib\u002Fcontentful.*","app\u002Fapi\u002Fdraft\u002F**","src\u002Fapp\u002Fapi\u002Fdraft\u002F**","app\u002Fapi\u002Frevalidate\u002F**","src\u002Fapp\u002Fapi\u002Frevalidate\u002F**","app\u002Fstudio\u002F**","src\u002Fapp\u002Fstudio\u002F**","main","dde9a06ab10bce3fe52058c783826590231f7df3","plugins\u002F.tmp\u002Fplugins\u002Fplugins\u002Fvercel\u002Fskills\u002Fcms","https:\u002F\u002Fgithub.com\u002Fwxdwu\u002Fnew-api","https:\u002F\u002Fgithub.com\u002Fwxdwu","24120128@bjtu.edu.cn","I am Xudong Wu，a student in beijing jiaotong University, \"I am eager to learn on GitHub and contribute my part to this community.","2026-10-05 17:26:50",{"source":8,"skillName":5,"commands":84,"prompt":94,"zipAvailable":95},[85,88,91],{"tool":86,"command":87},"npx","npx skills add wxdwu\u002Fnew-api --skill cms",{"tool":89,"command":90},"bunx","bunx skills add wxdwu\u002Fnew-api --skill cms",{"tool":92,"command":93},"pnpm","pnpm dlx skills add wxdwu\u002Fnew-api --skill cms","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fwxdwu\u002Fnew-api\u002Fblob\u002Fmain\u002Fplugins\u002F.tmp\u002Fplugins\u002Fplugins\u002Fvercel\u002Fskills\u002Fcms\u002FSKILL.md",true,[97],{"path":98,"resourceType":99,"resourceTypeDesc":100,"sizeBytes":101,"sourceHtmlUrl":102},"agents\u002Fopenai.yaml","REFERENCE","参考文档",169,"https:\u002F\u002Fgithub.com\u002Fwxdwu\u002Fnew-api\u002Fblob\u002Fmain\u002Fplugins\u002F.tmp\u002Fplugins\u002Fplugins\u002Fvercel\u002Fskills\u002Fcms\u002Fagents\u002Fopenai.yaml",[104,111,118,125,132,139],{"skillNo":105,"name":106,"nameZh":6,"description":107,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":108,"categories":109,"keywords":110,"publishedAt":19},"01M7E8SVA2N0JA09RW1XJYXCWX","Zotero","Use Zotero Desktop from Codex to enable\u002Fprobe the local API, search a local Zotero library, list items\u002Fcollections\u002Ftags, export BibTeX, insert citation keys into LaTeX or Markdown drafts, read indexed full text when requested, and import BibTeX\u002FRIS records into Zotero through the connector server. Use when the user mentions Zotero, citations, references.bib, BibTeX export, local Zotero API, localhost:23119, or adding citations from a Zotero library.","https:\u002F\u002Fgithub.com\u002Fwxdwu\u002Fnew-api\u002Fblob\u002Fmain\u002Fplugins\u002F.tmp\u002Fplugins\u002Fplugins\u002Fzotero\u002Fskills\u002Fzotero\u002FSKILL.md",[],[],{"skillNo":112,"name":113,"nameZh":6,"description":114,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":115,"categories":116,"keywords":117,"publishedAt":19},"01M7E8ST1FJAD5PWTW2FN1NSK0","zoom-apps-sdk","Use when using Apps SDK.","https:\u002F\u002Fgithub.com\u002Fwxdwu\u002Fnew-api\u002Fblob\u002Fmain\u002Fplugins\u002F.tmp\u002Fplugins\u002Fplugins\u002Fzoom\u002Fskills\u002Fzoom-apps-sdk\u002FSKILL.md",[],[],{"skillNo":119,"name":120,"nameZh":6,"description":121,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":122,"categories":123,"keywords":124,"publishedAt":19},"01M7E8SRW074NNNCZRCZA5T3Z1","setup-zoom-websockets","Use when building Zoom WebSockets.","https:\u002F\u002Fgithub.com\u002Fwxdwu\u002Fnew-api\u002Fblob\u002Fmain\u002Fplugins\u002F.tmp\u002Fplugins\u002Fplugins\u002Fzoom\u002Fskills\u002Fwebsockets\u002FSKILL.md",[],[],{"skillNo":126,"name":127,"nameZh":6,"description":128,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":129,"categories":130,"keywords":131,"publishedAt":19},"01M7E8SPCFMD3YVPRX2MW8X3RA","setup-zoom-webhooks","Use when building Zoom webhooks.","https:\u002F\u002Fgithub.com\u002Fwxdwu\u002Fnew-api\u002Fblob\u002Fmain\u002Fplugins\u002F.tmp\u002Fplugins\u002Fplugins\u002Fzoom\u002Fskills\u002Fwebhooks\u002FSKILL.md",[],[],{"skillNo":133,"name":134,"nameZh":6,"description":135,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":136,"categories":137,"keywords":138,"publishedAt":19},"01M7E8SMCH4RPCX8YSBNGWQCMP","zoom-virtual-agent-web","Zoom Virtual Agent SDK for web embeds. Use for campaign or entry ID chat launch, event-driven controls, user context updates, and CSP-safe deployment.","https:\u002F\u002Fgithub.com\u002Fwxdwu\u002Fnew-api\u002Fblob\u002Fmain\u002Fplugins\u002F.tmp\u002Fplugins\u002Fplugins\u002Fzoom\u002Fskills\u002Fvirtual-agent\u002Fweb\u002FSKILL.md",[],[],{"skillNo":140,"name":141,"nameZh":6,"description":142,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":143,"categories":144,"keywords":145,"publishedAt":19},"01M7E8SJCAY323E36Z8SAP8H4M","zoom-virtual-agent-ios","Zoom Virtual Agent iOS integration via WKWebView. Use for Swift\u002FObjective-C script injection, message handlers, support_handoff relay, and URL routing policies.","https:\u002F\u002Fgithub.com\u002Fwxdwu\u002Fnew-api\u002Fblob\u002Fmain\u002Fplugins\u002F.tmp\u002Fplugins\u002Fplugins\u002Fzoom\u002Fskills\u002Fvirtual-agent\u002Fios\u002FSKILL.md",[],[]]