A client rang me in a panic back in 2021. They'd relaunched their e-commerce catalogue — 4,200 product pages — on a headless Contentful setup with a Next.js front-end. Their agency had sold them on the pitch: modern stack, lightning fast, Google will love it. Six weeks post-launch, organic traffic was down 61%. Not crawl errors. Not manual penalties. Just... gone.
I've seen this pattern too many times now. And the frustrating part? The SSR was technically working. Pages were rendering on the server. HTML was being returned. But there were about seven other places where the whole thing was quietly falling apart, and nobody had thought to check.
This isn't a post about whether headless is good or bad — it clearly can be excellent. It's about the specific, solvable ways that SSR on a headless stack goes wrong for SEO, and what you actually do about it.
---
The Myth That SSR Automatically Fixes Headless SEO
Here's the thing. When client-side rendering became mainstream around 2016-2018, the SEO community had a collective meltdown (justifiably). Google's crawler was inconsistent with JavaScript execution, content would go unindexed, and SPA sites were bleeding rankings. So the industry swung hard toward SSR as the cure.
And it is better than pure CSR. But "better" doesn't mean "sorted."
SSR solves the rendering problem. It does almost nothing about caching strategy, crawl budget, canonical confusion, or the metadata pipeline between your CMS and your HTML <head>. Those are entirely separate failure modes. And in a headless architecture, every single one of them involves at least two systems — the CMS and the front-end framework — that need to agree on what to do.
They often don't.
---
Where SSR Actually Breaks SEO in a Headless Stack
The Time-to-First-Byte Problem
SSR is only fast if your server is fast. On a headless setup, your Next.js or Nuxt server has to fetch content from the CMS API before it can respond. If Contentful (or Sanity, or Storyblok, or whichever) is having a slow moment, your TTFB balloons. I've seen TTFB spike past 3 seconds on poorly configured SSR setups during CMS API cold starts.
Google uses TTFB as a signal for crawl scheduling. Slow responses mean Googlebot crawls fewer pages per session. On a large catalogue site, that directly translates to pages stuck in the crawl queue for weeks.
Canonical Tags Generated at Runtime
This one catches people off guard. In a traditional CMS like WordPress, canonical tags are baked into the theme or an SEO plugin. In a headless setup, your canonical logic lives in your front-end code — maybe in a Next.js <Head> component, maybe in a layout wrapper. The CMS has no idea what canonical you're rendering.
So what happens when a product URL has query parameters for sorting or filtering? Or when your CMS returns a page slug that's slightly different from your routing logic? You end up with canonical tags that either point to the wrong URL or are missing entirely. I caught this on a Seahawk project for a UK retailer last year — 800 pages were canonicalising to /?page=1 because the pagination logic was passing the wrong prop to the SEO component. Took two days to find. Three lines to fix.
Metadata Pipelines with No Fallbacks
Every headless CMS lets you add SEO metadata fields — meta title, description, OG tags. Great. But what happens when an editor publishes a page and forgets to fill them in? In WordPress with Yoast, you'd get a generated fallback. In a headless setup, if your front-end component doesn't have explicit fallback logic, you get an empty <title> tag. Or worse — you get the raw field name echoing into the HTML.
Always build the fallback chain explicitly: seoTitle ?? pageTitle ?? siteName. Every field. No exceptions.
---
The Caching Layer Nobody Thinks Hard Enough About
ISR — Incremental Static Regeneration in Next.js — is genuinely clever. You get mostly-static performance with the ability to revalidate on a schedule. But for SEO, the revalidation window is a decision with real consequences.
Set revalidate: 3600 (one hour) and your content edits won't be seen by Googlebot for up to an hour after publish. That's fine for a blog. For a news site or a flash-sale e-commerce page, it's a disaster. I had a client who ran a 4-hour limited sale and spent 45 minutes of it with a cached "sold out" page because nobody had thought about the ISR window when the discount campaign was planned.
The fix isn't always "revalidate more aggressively." More frequent revalidation means more origin load. The real fix is on-demand revalidation — trigger a cache purge from your CMS webhook when content is published. Next.js has supported on-demand ISR since v12.2. Contentful, Sanity, and Storyblok all support outgoing webhooks. Wire them together. It takes about an afternoon.
---
Crawl Budget and the Headless URL Surface
Traditional CMS platforms have years of convention around URLs — taxonomies, pagination, canonical handling for archives. Headless setups give you total freedom, which means you have to make all those decisions yourself, in code.
Freedom is dangerous when you're not paying attention.
A headless product catalogue with faceted filtering can easily generate tens of thousands of unique URLs — /products?colour=red&size=M&sort=price-asc and every permutation thereof. If your SSR layer is rendering all of those with unique HTML and no canonical pointing back to the base URL, you've just handed Googlebot an infinite maze.
A few things I do on every headless build:
- Block all query-parameter URLs in
robots.txtthat aren't SEO-significant - Implement a single canonical on all filtered/sorted variants pointing to the clean base URL
- Use
<meta name="robots" content="noindex, follow">on paginated pages beyond page 2 for smaller sites - Audit the XML sitemap against what Googlebot is actually crawling (via Google Search Console's Coverage report) — the two are rarely the same on a first pass
And please — generate your sitemap dynamically from your CMS, not statically at build time. A sitemap that only reflects content from your last deploy is useless if editors publish 40 new pages between deployments.
---
The Structured Data Gap
Headless CMSs are brilliant at structured content. Schemas, field types, references — Sanity and Contentful both model data beautifully. But structured data for SEO (JSON-LD schemas — Product, Article, BreadcrumbList, etc.) is a different thing entirely.
Most headless front-end setups I audit have either no JSON-LD at all, or a single generic WebSite schema bolted onto the layout. That's a miss. On a product page, you want Product schema with price, availability, and review data pulled live from your CMS. On a recipe or how-to page, the appropriate schema can directly influence rich results in Google.
The implementation isn't complicated. In Next.js, drop your JSON-LD into a <script type="application/ld+json"> tag inside <Head>, populate it from your page props, and test it in Google's Rich Results Test. What is complicated is making sure your CMS content model surfaces the right fields for the front-end to consume. That's a content architecture conversation, not a dev ticket.
---
Fixing the Metadata Pipeline End-to-End
Let me give you the exact checklist I run on every headless SEO audit. Not conceptual. Actual steps.
- Verify rendered HTML — Use
curl -A "Googlebot" [your URL]and inspect the raw response. What does the<head>actually contain? Not what your browser shows after hydration. The raw server response. - Check canonical accuracy on 20 random pages — Especially product/category pages with parameters. Build a small script with
node-fetchto pull and parse canonicals at scale if the site is large. - Test TTFB from three locations — I use WebPageTest with Googlebot UA from London, Frankfurt, and Virginia. If any location is above 800ms consistently, dig into your CMS API response times before anything else.
- Audit your sitemap against GSC — Export the Coverage report from Search Console. Compare "Valid" URLs to your sitemap. Any URL in the sitemap that's "Excluded" needs investigation.
- Check for duplicate `<title>` and `<meta description>` tags — Happens more than you'd think when layout components and page-level components both try to write metadata.
- Test on-demand revalidation end-to-end — Publish a content change in your CMS. How long before it's live on the server-rendered page? If it's measured in hours, wire up the webhook.
- Validate structured data on representative page types — Product, Article, FAQ at minimum. Use Google's Rich Results Test on the live URLs, not just locally.
---
The Tools I Actually Use
Not a theoretical list. This is what's open on my machine when I'm in the middle of a headless SEO fix.
- Screaming Frog — Crawl the live site in rendering mode to see what Googlebot sees. Set the rendering mode to "None" first to see raw SSR output, then compare to "JavaScript" mode.
- WebPageTest — TTFB, server response waterfall, CDN edge hit/miss headers.
- Google Search Console — Coverage report, URL Inspection for specific pages, Core Web Vitals by page type.
- Postman or `curl` — For manually querying CMS APIs to check what data is actually being returned to the SSR layer.
- Next.js built-in logging — Often overlooked. Turning on verbose logging during a staging audit will surface exactly where your render is waiting.
Honestly, 80% of headless SEO issues I find are visible from Screaming Frog alone if you know what to look for.
---
FAQ
Does Next.js with SSR guarantee good SEO?
No. SSR means your HTML is rendered on the server before it reaches the client — that's necessary but not sufficient. You still need correct canonical tags, a sensible sitemap, proper metadata, structured data, and fast server response times. SSR removes the JavaScript-rendering problem. It doesn't remove the architecture problems.
Is Contentful better for SEO than Sanity?
Neither CMS directly affects your SEO — they're headless, so they have no opinion on your rendered HTML. The question is which one makes it easier to model SEO-relevant content fields. Both have SEO field plugins. Sanity's GROQ query language gives you more flexibility in shaping the exact data your front-end needs, which can make it easier to build a clean metadata pipeline. But that's a developer experience argument, not an SEO argument.
How do I handle hreflang in a headless setup?
Same way you'd handle any metadata — generate it server-side from your CMS data and inject it into <head> on every page. The complexity is in maintaining the locale-to-URL mapping in your CMS and making sure the front-end consumes it correctly. If you're on Next.js, the i18n config handles a lot of the routing side; you still need to explicitly render the <link rel="alternate" hreflang="..."> tags from your content data.
Should I use SSG instead of SSR for better SEO?
Depends on your content update frequency. Full static generation (SSG) gives you the fastest possible TTFB — everything pre-built at deploy time — but means content updates only go live on redeploy unless you're using ISR. For a mostly-static marketing site, SSG with on-demand ISR is probably the right call. For a large catalogue with frequent inventory changes, SSR with aggressive CDN caching and short-lived cache headers is more appropriate.
---
The uncomfortable truth is that headless stacks put more SEO responsibility in the hands of developers than any previous CMS architecture. There's no plugin that installs and handles it. Every decision — from canonical logic to sitemap generation to structured data — is a code decision. Which means every one of those decisions can be wrong, and most teams don't audit them until rankings are already moving in the wrong direction.
Get ahead of it. Crawl your own site like Googlebot would. The problems are almost always findable before Google finds them for you.
