Skip to main content

Sanity Setup

Environment model

One Sanity project, one dataset: production. There is no staging dataset.
  • Production deployment (VERCEL_ENV=production): tokenless reads, published perspective, Sanity CDN — only published articles, fully public for SEO/AEO.
  • Every other deployment (previews, the staging site, local dev): reads the same production dataset with a viewer token and the drafts perspective — published articles plus drafts, with drafts tagged in the UI (card badge + article banner). Drafts stay invisible to the public because they require the token.
NEXT_PUBLIC_SANITY_DATASET is no longer read; remove it from Vercel env.

Required environment variables

  • NEXT_PUBLIC_SANITY_PROJECT_ID
  • NEXT_PUBLIC_SANITY_API_VERSION
  • SANITY_REVALIDATE_SECRET
  • SANITY_PREVIEW_READ_TOKEN (legacy name SANITY_STAGING_READ_TOKEN also works) — viewer token for non-production draft previews
  • INDEXNOW_KEY — search-engine ping on publish

Files

  • studio/sanity.config.ts configures the standalone Sanity Studio
  • studio/sanity.cli.ts configures CLI API defaults
  • studio/schemaTypes/authorType.ts
  • studio/schemaTypes/categoryType.ts
  • studio/schemaTypes/postType.ts
The Studio is a separate application from the Next.js website. Run it from the studio/ directory with npm run dev, validate it with npm run build, and deploy it with npm run deploy. It has a single workspace on the production dataset — the dataset is hardcoded on purpose so a deploy can never silently point the Studio somewhere else. Drafts saved here are the preview content for non-production deployments. The post body schema supports text, images, dividers, tables, and a callToAction object with a heading, description, trust line, button label, and absolute button URL. Posts also support a faq array (question + plain-text answer), authored in the Studio. It renders as a visible FAQ section at the end of the article and as FAQPage JSON-LD.

Public readability (SEO/AEO)

The blog is intentionally fully public: the production dataset is public, the production site reads without any token, and search/answer-engine crawlers must be able to see everything. Two rules keep it that way:
  • Blog document ids must never contain a dot. Sanity hides dotted “path” ids (post.<slug>) from unauthenticated requests even on public datasets — publishing with dotted ids silently blanks the public blog. Use post-<slug> / category-<slug> / author-<slug>, or let Sanity generate plain ids (Studio-created documents are safe by default).
  • Tokenless reads go through Sanity’s CDN (useCdn is enabled when no read token is configured) for faster public TTFB; non-production deployments use a viewer token + the drafts perspective on the live API so drafts appear immediately.

Revalidation webhook

The app exposes POST /api/revalidate/sanity. Two authentication forms, each sufficient:
  • Sanity webhook deliveries: a valid sanity-webhook-signature header, signed with SANITY_REVALIDATE_SECRET (set the same value in the webhook’s Secret field in manage.sanity.io — Sanity signs every delivery automatically, with replay-bounded timestamps).
  • Manual/ops calls: Authorization: Bearer <SANITY_REVALIDATE_SECRET>.
(Query-string and custom-header forms were removed — query params leak into logs and webhook delivery history.) Optional request body:
Behavior:
  • Revalidates blog listing cache tags
  • Revalidates blog post tag for the provided slug
  • Revalidates /blog
  • Revalidates /blog/[slug] when slug is present
  • Revalidates /sitemap.xml
  • Submits the article URL to IndexNow (best-effort) when the slug resolves to a published, indexable post
Configure a Sanity webhook (manage.sanity.io → API → Webhooks) on post create/update/delete for the production dataset that POSTs to this endpoint, with SANITY_REVALIDATE_SECRET in the webhook’s Secret field and a {"slug": slug.current} projection — with publishing happening directly in Sanity, this webhook is the only post-publish hook. Duplicate deliveries are harmless: revalidation and the IndexNow ping are both idempotent.

Authoring and publishing

Content is authored directly in Sanity — there is no git-side pipeline. Agents create drafts via the Sanity MCP (or API) following .agents/skills/author-blog-posts/SKILL.md; humans review and publish in the Studio. The publish webhook above handles revalidation and IndexNow.

Redesign data contract

The directory pills are read from category documents and post cards resolve categories[] references. The Framer sample categories are not imported. The redesign requires no schema migration or content writes. Authors, hero images, body blocks and FAQs continue using the existing Studio fields. Author linkedinUrl and xUrl fields render as profile links in the article byline; empty fields do not create social controls. getBlogRelatedPosts(slug) searches all eligible articles, excluding that slug, and returns up to three ranked by shared category references, newest publication date, then document ID. It uses the same published/draft perspective and cache tags as other blog reads.