Guide Sanity SEO

Sanity stores content as JSON and emits zero HTML — votre frontend decides votre SEO. Rendering modes, Portable Text, the SEO object, sitemaps, draft-mode noindex, JSON-LD, and plugins.

Première publication : 27 juin 2026 · Dernière mise à jour : 3 août 2026 · Advanced
Langues

Sanity is a CMS headless que stores content as structured JSON in its Content Lake and produces aucun HTML — so votre frontend framework (Suivant.js, Astro, Remix) decides everything à propos de votre SEO. The unique biggest lever is rendering: SSG and SSR ship complet HTML and are safe; CSR is risky parce que LLM-crawler rendering contracts vary by provider. Portable Text is a JSON AST, pas HTML, so the frontend doit serialize it. Construire a reusable SEO object in le contenu model, generate sitemaps and JSON-LD programmatically, garder drafts out of the index with an X-Robots-Tag: noindex header, and utiliser sanity-plugin-seo (pas the deprecated Yoast pane). SEO success in Sanity comes from intentional structure, pas plugins.

TL;DR — Sanity is content-neutral: it stores structured JSON in le contenu Lake and emits zero HTML, so the frontend’s rendering mode decides everything. SSG and SSR ship complet HTML and are safe; CSR is the risky un parce que LLM robots d’exploration don’t execute JavaScript, and Google deprecated dynamic rendering as a workaround. Portable Text is a JSON AST que the frontend doit serialize. Construire a reusable seo object in le contenu model with overrides (pas requirements) via coalesce(), generate sitemaps and JSON-LD programmatically from existing fields, and garder drafts out of the index with an X-Robots-Tag: noindex header on every preview réponse. Utiliser sanity-plugin-seo — jamais the deprecated Yoast pane — but remember “SEO success in Sanity comes from intentional structure, not plugins.”

Sanity is content storage, pas a publisher

A utile premier principle: Sanity touches votre content, jamais votre rendering. Le contenu Lake stores everything as structured JSON, queried with GROQ (Sanity’s requête language) or REST. Sanity Studio — the editing UI — is a React single-page app que usually lives on its propre .sanity.studio subdomain and isn’t something vous index. Nothing in ce stack produces HTML.

Evidence for this claim Sanity Content Lake is queried using APIs and GROQ; it is distinct from a site's frontend rendering layer. Scope: Sanity-hosted content data and query APIs. Confidence: high · Verified: Sanity: Content Lake

So the entier SEO surface lives in votre frontend framework. As Webstacks put it, “Sanity handles content storage and editing, pendant que votre frontend framework handles SEO output.” That’s pas a limitation — it’s the whole headless model. Sanity’s propre team frames it bien: “Quand en utilisant Sanity as a CMS, you’re doing headless SEO. Although ce approach exige thoughtful technical implementation, it peut liberate votre content to aider vous reach plus utilisateurs à travers tout channels.” The fonctionner is réel, but it’s the même fonctionner as quelconque headless construire (voir SEO pour a CMS headless).

Rendering mode is the foundation

Parce que the frontend builds the HTML, the rendering decision is the unique la plupart consequential SEO choice on a Sanity site. Four modes:

SSG — Static Site Generation. Content is récupéré from Sanity at construire temps and baked into static HTML served from a CDN. Best-case SEO: fully-rendered HTML on premier requête, fast TTFB. Ideal pour blog posts, marketing pages, and product/landing pages. The tradeoff is freshness — modifié content nécessite a rebuild (ISR solves ce).

SSR — Rendu côté serveur. Content is récupéré per requête and the complet HTML is rendered on le serveur. Excellent SEO, toujours fresh. Utiliser it pour frequently mis à jour content. Tradeoff is infrastructure cost and slightly plus élevé TTFB que static fichiers.

ISR — Incremental Static Regeneration (Vercel/Suivant.js). Static pages regenerate in the background, ideally triggered by a Sanity webhook on publish so lune page rebuilds the moment an editor hits publish. Ce is the meilleur of SSG’s speed and SSR’s freshness pour content que changements on the order of hours or days.

CSR — Rendu côté client. An HTML shell ships, alors JavaScript récupère from Sanity and builds the DOM in le navigateur. Ce is the worst SEO option: Googlebot has to queue lune page pour a plus tard render wave, timing is unpredictable, and AI robots d’exploration and nombreux autre bots voir seulement the shell. CSR is acceptable seulement behind authentication pour content vous don’t vouloir indexé anyway.

And don’t reach pour the old shortcut: Google has deprecated dynamic rendering (serving prerendered HTML to bots, CSR to utilisateurs). Officially, “dynamic rendering was a workaround and pas a long-term solution,” and Google now recommends “server-side rendering, static rendering, or hydration.” Pour a Sanity site, que signifie SSG or SSR — pas a bot-detection hack. (Complet rendering treatment in JavaScript SEO.)

Evidence for this claim Google describes dynamic rendering as a workaround rather than a recommended long-term solution and recommends server-side rendering, static rendering, or hydration. Scope: Google Search guidance for JavaScript sites. Confidence: high · Verified: Google: Dynamic rendering

Portable Text — votre corps copy is a JSON AST

Ce is the la plupart Sanity-specific chose on lune page. Sanity stores rich text as Portable Text: a JSON abstract syntax tree, pas HTML. Corps copy in le contenu Lake semble comme an array of typed blocks, pas <p> tags. Votre frontend has to serialize it into HTML avant quelconque robot d’exploration peut lire it:

  • @portabletext/react — pour React/Suivant.js.
  • @portabletext/to-html — framework-agnostic (Astro, Remix, anything).
  • The old @sanity/block-content-to-react is deprecated — don’t utiliser it.

Si vous render server-side or at construire temps, serialization se produit avant lune page is sent and robots d’exploration obtenir complet HTML. Si vous render client-side, the serialization runs in le navigateur. Parce que LLM-crawler rendering contracts vary by provider, CSR Portable Text creates a coverage risk que raw HTML avoids. There’s aussi a handy GROQ helper: pt::text(body) extracts tout the plain text from a Portable Text field as a string, qui is perfect pour feeding JSON-LD descriptions sans a separate editor field.

Content modeling pour le SEO — the reusable SEO object

The cleanest pattern, and the un Sanity’s propre course teaches, is a unique reusable seo object ajouté to every document type — pas SEO fields copy-pasted per type. The clé design principle from Sanity Apprendre: “Pour the benefit of content authors, fields relevant to SEO ne doit pas toujours be requis. À la place, ils devrait be utilisé to override some existing content, quand provided.”

En pratique que signifie SEO fields are overrides with fallbacks, implemented with GROQ’s coalesce():

"title": coalesce(seo.title, title, ""),
"description": coalesce(seo.description, excerpt, "")

A minimum viable seo object: metaTitle (≤65 chars), metaDescription (≤155 chars), canonicalUrl, openGraphImage (1200×630), and a noIndex boolean. Ajouter Sanity validation rules to enforce ceux character limites avant publish — so editors catch problems the platform voudrait sinon let via. Utiliser a slug type with a custom generation function, and utiliser références pour lien internes so URL changements propagate automatically. Ajouter a hideFromSearch boolean and filter it out of les deux le sitemap GROQ requête and lune page’s robots metadata.

Metadata, sitemaps, and canonicals — tout construit in the frontend

Metadata. In Suivant.js App Router, utiliser the exported generateMetadata() function (pas inline <head> tags) so metadata doesn’t duplicate à travers nested layouts. Construire un server-side helper que takes a GROQ result and renvoie the framework’s metadata object — implement it une fois, utiliser it everywhere. Ouvrir Graph images peut be generated dynamically with Suivant.js Edge OG generation, pulling content straight from Sanity.

Sitemaps. There’s aucun auto-sitemap. Vous construire it from a GROQ requête → URL array → XML (sitemap.ts in Suivant.js, @astrojs/sitemap in Astro). Toujours filter drafts and hidden pages:

*[_type in ["page", "post"] && defined(slug.current) && hideFromSearch != true]

Utiliser _updatedAt (pas _createdAt or a manual date) pour lastModified, garder individual sitemap fichiers sous 50 000 URLs with an index pour plus grand sites, and trigger regeneration via a Sanity webhook on publish/unpublish so it jamais goes stale.

Canonicals. Définir URL canoniques from le contenu model, par défaut to a self-referencing canonical, and construire absolute URLs from a unique SITE_URL so the canonical jamais drifts. (Mechanics in Canonicalization.)

Données structurées — generate it, don’t author it

The correct pattern is to generate JSON-LD programmatically from existing content fields at render temps — jamais construire a separate JSON-LD editor surface in Studio, qui simplement creates drift entre content and markup. Roboto Studio’s rule is blunt: “Derive JSON-LD from existing fields, at render time, in code.” Sanity agrees — “JSON-LD is a powerful façon to provide données structurées to moteur de recherches — fortunately données structurées is ce que Sanity fait meilleur.”

Utiliser pt::text(body) to pull plain text pour schema descriptions, the TypeScript package schema-dts pour type safety, and render it with <script type="application/ld+json">. Remarque que “the JSON-LD markup peut be rendered anywhere in lune page — it doesn’t besoin to be stored à l’intérieur the <head>.” Priority types pour content sites: Article/BlogPosting, BreadcrumbList, FAQPage, Organization, WebSite.

Keeping drafts out of Google — the real-incident pitfall

Sanity’s perspective system contrôle draft visibility: published renvoie seulement production content; drafts renvoie draft documents pour preview. Toujours utiliser the published perspective in production API calls. But que alone isn’t suffisant, parce que draft URLs leak. As un developer recounted, the danger is “a client panicking parce que an unpublished landing page showed up dans la recherche Google Console. Si a content editor shares que URL in Slack, and someone clicks it from a navigateur that’s alors crawled (it se produit), the draft content peut fin up indexé.”

The reliable defense is a multi-layer un:

  1. Utiliser the published perspective in production; jamais expose an unprotected preview endpoint.
  2. Ajouter an X-Robots-Tag: noindex HTTP header to every draft-mode/preview réponse — même si a draft URL leaks and obtient crawled, Googlebot respects the header and won’t index it. (Don’t rely on robots.txt pour ce; a header is plus reliable que a disallow pour preview pages.)
  3. Validate the preview secret server-side; jamais reflect it in the redirection URL, and scope the preview-enable endpoint to votre Studio’s origin.

Robots, redirections, image SEO, and hreflang

robots.txt is a static frontend fichier (app/robots.ts in Suivant.js). Block Studio paths seulement si they’re on the même domain (Disallow: /studio/); don’t essayer to block preview pages with it.

Redirections are a genuine workflow win: store les as Sanity documents (from, to, statusCode) so le contenu team peut manage redirections sans a developer deploy, alors implement les in Suivant.js middleware.ts or a CDN/Worker couche (jamais in Sanity itself). Ajouter validation rules to prevent loops and invalid paths.

Image SEO: utiliser Sanity’s image CDN with ?auto=format pour automatic WebP/AVIF, manage texte alternatif at the asset level with document-level overrides, utiliser vanity filenames au lieu de hashed URLs, and définir hotspot/crop so automatic crops garder the subject — qui matters la plupart pour OG images.

Hreflang: model locale variants as separate Sanity documents referencing the canonical version, generate hreflang from a GROQ requête returning tout locale slugs, and give every locale page a self-referencing canonical — jamais canonical a non-English page to the English un.

Plugins — utile, pas a substitute

  • sanity-plugin-seo — the principal recommendation (22k+ downloads). Live SEO score, meta/OG preview, social cards, robots contrôler, hreflang, readability. The AI tier adds keyword suggestions and meta generation.
  • sanity-plugin-seofields — an alternative: 39 schema.org types, an SEO Health Dashboard, and Suivant.js helper functions (buildSeoMeta()).
  • sanity-plugin-seo-paneDEPRECATED. Ce is the old Yoast integration, supprimé due to an outdated dependency. It encore montre up in old tutorials. Don’t utiliser it.

The honest framing, from Yanatiev: “SEO success in Sanity comes from intentional structure, pas plugins.” Plugins surface fields and give editorial feedback — ils don’t replace proper content modeling.

AI search and AEO

The 2026 wrinkle: LLM training and retrieval robots d’exploration (Anthropic, OpenAI, Courant Explorer) typically don’t execute JavaScript today, so CSR Portable Text is largely invisible to les regardless of si Googlebot peut render it — but ce is provider-specific and pas a guaranteed contract. Google’s Gemini pipeline is the notable exception, since it reuses Googlebot’s propre rendering infrastructure, and autre providers’ behavior peut modifier. Treat “no rendering” as the safe assumption to construire pour, pas a permanent law. Au-delà rendering, two Sanity-friendly tactics: serve clean markdown via content negotiation quand Accept: text/markdown is présent (Sanity’s @portabletext/markdown package converts Portable Text with aucun supplémentaire authoring), and monitor Bing specifically since Bing’s index feeds ChatGPT’s browse réponses. On llms.txt, Sanity’s propre Knut Melvær is skeptical — “Probably don’t do llms.txt” pour grand sites, parce que the all-or-nothing scope and file-size limites faire it impractical; per-page content negotiation is plus granular. Roboto Studio’s summary is the correct mindset: “AEO and SEO live on the même plumbing, so we treat les as un job.” (Plus in AI Search.)

The myths worth killing

  • “Sanity does SEO automatically like WordPress + Yoast.” Aucun. Zero HTML, zero automatic anything.
  • “Any rendering mode is fine if the content’s in Sanity.” Aucun. CSR breaks indexation and AI visibility; dynamic rendering is deprecated.
  • “Drafts can’t be indexed.” Ils peut, si a preview URL leaks. Utiliser a noindex header.
  • “Portable Text is HTML.” It’s a JSON AST que the frontend doit serialize.
  • “You need a JSON-LD editor in Studio.” Aucun — generate it from existing fields.

Add an expert note

Pin an expert quote

New person? Create their unclaimed profile at /admin/experts/ → Pin a quote first.