> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cfosilvia.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Sanity setup

# 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:

```json theme={null}
{
  "slug": "your-post-slug"
}
```

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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.