Technical SEO for Sanity and Next.js that ships itself

Metadata, Open Graph, sitemaps and JSON-LD are solved problems if you wire them once. Here is the technical SEO setup baked into Turbo Start Sanity.

Most SEO regressions are not strategy failures. They are a missing <title>, an Open Graph card that never rendered, a sitemap that forgot a page. The fix is to make the correct output the automatic output, so editors cannot ship a page that is invisible to search. Here is how Turbo Start Sanity does exactly that.

Metadata with a fallback chain

Next.js exposes a generateMetadata hook per route. Turbo Start Sanity centralises it in one getSEOMetadata helper that resolves site-wide defaults from the Sanity Settings document, then layers the page's own values on top:

The key idea is the cascade: seoTitle falls back to title, seoDescription to description, and site-wide defaults catch anything still empty. Nothing renders blank, ever — even before an editor fills the SEO tab.

Description fields that behave

The blog schema nudges editors toward good descriptions with validation rather than a wall of text. It warns below 140 characters and again above 160, the window that renders cleanly in search results:

Open Graph without design work

Every page gets a social card from a dynamic OG image route. The metadata helper builds a URL like /api/og?type=blog&id=<docId>, and the route renders an image from the document's own title and image. Editors get a polished card for free, and you never maintain a folder of hand-cut PNGs.

JSON-LD from data you already have

Structured data should never be a field editors fill in. Generate it at render time from the document, with the same fallback chain as your metadata:

Emit it in a <script type="application/ld+json"> tag and search engines get an unambiguous description of the page.

Two switches, two jobs

The schema exposes seoNoIndex and seoHideFromLists, and the distinction matters. seoNoIndex adds a noindex robots directive so a page stays reachable but drops out of the index. seoHideFromLists filters the document out of GROQ list queries, so it never surfaces in feeds or the sitemap. One controls crawlers; the other controls your own listings. Conflating them is how a "hidden" page ends up indexed anyway.

Sitemaps that tell the truth

Build the sitemap from the same queries that power your lists, honour both visibility switches, and use _updatedAt as the lastmod signal so crawlers see genuine freshness rather than a build timestamp.

Make it un-regressable

The reason this holds up over hundreds of pages is that correctness is structural, not disciplined. Fallbacks mean an empty field still renders something valid. Validation warns at author time. JSON-LD and OG images derive from data, so they cannot drift from the content. Wire it once, and SEO stops being a checklist someone forgets.