← Back to blog

Technical SEO · May 31, 2026 · 12 min read

Headless CMS SEO in 2026: Sanity, Strapi, Contentful with Next.js Done Right

Headless architecture gives developers complete control — and also lets them break SEO in ways monolithic CMSes prevent. Here's the production checklist for Sanity/Strapi/Contentful + Next.js setups that actually rank.

By FluxWriter Team

Headless CMS SEO in 2026: Sanity, Strapi, Contentful with Next.js Done Right

The headless trade-off: control for complexity

Headless CMSes (Sanity, Strapi, Contentful, Storyblok, Payload) separate the content backend from the frontend. The content team uses a clean editor; the development team renders content via API into any frontend framework (Next.js, Astro, SvelteKit, Nuxt).

What you gain:

What you risk:

This guide covers the production-ready checklist for headless CMS + Next.js setups. Most patterns apply equally to other frameworks.

The 12 things you must get right

1. Server-side rendering (not client-side)

Critical. Google can crawl client-side rendered (CSR) React apps in 2026, but with significant latency penalty. Pages render emptily, Google waits for JS to execute, then re-crawls.

Practical impact:

For Next.js: use SSR (getServerSideProps) for dynamic content, SSG (getStaticProps) for content that's mostly static. Avoid pure CSR for any SEO-critical page.

For Astro: SSR is the default — good.

For SvelteKit: +page.server.ts for SSR.

2. Sitemap.xml generation

Headless CMSes don't ship sitemap.xml by default. You generate it from API responses at build time (SSG) or on-demand (SSR).

Next.js with Sanity example:

// app/sitemap.ts
import { sanityClient } from '@/lib/sanity';

export default async function sitemap() {
  const posts = await sanityClient.fetch(
    `*[_type == "post" && published == true]{ slug, _updatedAt }`
  );

  return [
    {
      url: 'https://yoursite.com',
      lastModified: new Date(),
      priority: 1.0,
    },
    ...posts.map((post) => ({
      url: `https://yoursite.com/blog/${post.slug.current}`,
      lastModified: new Date(post._updatedAt),
      priority: 0.8,
    })),
  ];
}

Verify the sitemap appears at /sitemap.xml. Submit to Search Console.

3. robots.txt

Headless CMSes don't generate robots.txt automatically either. Create it at the framework level.

Next.js example (app/robots.ts):

export default function robots() {
  return {
    rules: [
      { userAgent: '*', allow: '/' },
      { userAgent: '*', disallow: ['/admin', '/api'] },
    ],
    sitemap: 'https://yoursite.com/sitemap.xml',
  };
}

4. Meta tags per page

Each page needs unique meta title, description, and Open Graph tags. With headless, these come from the CMS content.

Next.js metadata pattern:

export async function generateMetadata({ params }) {
  const post = await sanityClient.fetch(
    `*[_type == "post" && slug.current == $slug][0]`,
    { slug: params.slug }
  );

  return {
    title: post.seoTitle ?? post.title,
    description: post.metaDescription,
    openGraph: {
      title: post.seoTitle ?? post.title,
      description: post.metaDescription,
      images: [post.featuredImage?.asset?.url],
    },
    alternates: {
      canonical: `https://yoursite.com/blog/${post.slug.current}`,
    },
  };
}

Common mistake: defaulting all pages to the site-wide meta tags. Each post should have its own.

5. Canonical URLs

Critical for headless. Without explicit canonicals, you can accidentally serve the same content on multiple URLs (with/without trailing slash, with query parameters, etc.) and Google treats them as duplicates.

Set canonical to the post's primary URL on every page. Pattern shown above in the metadata function.

6. Open Graph + Twitter Card tags

For social sharing CTR. Set explicitly:

openGraph: {
  title: post.title,
  description: post.metaDescription,
  type: 'article',
  publishedTime: post.publishedAt,
  authors: [post.author?.name],
  images: [
    {
      url: post.featuredImage?.asset?.url,
      width: 1200,
      height: 630,
    },
  ],
},
twitter: {
  card: 'summary_large_image',
  title: post.title,
  description: post.metaDescription,
  images: [post.featuredImage?.asset?.url],
},

7. Schema markup (JSON-LD)

Render JSON-LD as a script tag in the head per page. Covered comprehensively in our schema markup guide.

Headless implementation:

// In your page component
<script
  type="application/ld+json"
  dangerouslySetInnerHTML={{
    __html: JSON.stringify({
      '@context': 'https://schema.org',
      '@type': 'Article',
      headline: post.title,
      image: post.featuredImage?.asset?.url,
      author: { '@type': 'Person', name: post.author?.name },
      publisher: {
        '@type': 'Organization',
        name: 'Site Name',
        logo: { '@type': 'ImageObject', url: 'https://yoursite.com/logo.png' },
      },
      datePublished: post.publishedAt,
      dateModified: post._updatedAt,
    }),
  }}
/>

8. Image optimization

Headless setups commonly fail at image optimization. Two requirements:

Responsive images: Serve different sizes for different viewports. Next.js's <Image> component handles this; Sanity has built-in image transformation URLs.

Lazy loading: Below-fold images load only when scrolled into view. Default in Next.js <Image>.

Explicit dimensions: Width and height attributes required. Layout shift kills Core Web Vitals.

9. Internal linking

Most headless setups don't auto-suggest internal links the way WordPress with Rank Math does. You need to either:

Without internal linking, individual posts rank as standalone — losing the topical authority compounding that makes content marketing work.

10. IndexNow integration

Headless CMSes don't ping IndexNow by default. Implement a webhook from the CMS to your IndexNow endpoint.

Sanity → Vercel deploy + IndexNow ping pattern:

  1. Set up Sanity webhook fires on post publish
  2. Webhook hits a Next.js API route
  3. API route revalidates the page (ISR) AND pings IndexNow
// pages/api/webhook.ts
export default async function handler(req, res) {
  const { slug } = req.body;

  // Revalidate the page
  await res.revalidate(`/blog/${slug}`);

  // Ping IndexNow
  await fetch('https://api.indexnow.org/indexnow', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      host: 'yoursite.com',
      key: 'YOUR-INDEXNOW-KEY',
      urlList: [`https://yoursite.com/blog/${slug}`],
    }),
  });

  res.status(200).json({ ok: true });
}

Full implementation details in our IndexNow WordPress guide — same approach applies to headless.

11. ISR (Incremental Static Regeneration) for content updates

For SSG-rendered pages, content updates won't appear until next build by default. ISR regenerates pages on a schedule or on-demand.

Next.js ISR pattern:

// In your page
export const revalidate = 3600; // Regenerate at most once per hour

Or on-demand revalidation when content changes (triggered by CMS webhook).

Without ISR or scheduled rebuilds, your "headless" site falls behind the CMS state. Tutorials assume daily rebuilds; reality is content updates 10x/day for active sites.

12. Performance optimization

Headless + Next.js commonly hits Core Web Vitals issues:

LCP (Largest Contentful Paint):

INP (Interaction to Next Paint, replaced FID in 2024):

CLS (Cumulative Layout Shift):

Verify in Search Console → Core Web Vitals report. Anything < 75% "Good" needs work.

Sanity-specific tips

Strapi-specific tips

Contentful-specific tips

When NOT to go headless

Three signals headless is wrong for your use case:

1. Content team without developer support. Headless requires ongoing dev time for any SEO feature you'd get free in WordPress. If you don't have a dev resource, stay on WordPress + Rank Math.

2. Small site with infrequent updates. Below 50 posts and updating monthly, the SEO automation in monolithic CMSes (Yoast, Rank Math) outweighs the performance benefits of headless.

3. Budget under $5K/month for development. Headless's hidden cost is maintenance. Plugin updates that "just work" in WordPress require dev time in headless.

For most B2B SaaS in 2026, the sweet spot is: WordPress for the blog (mature SEO ecosystem) + headless for the product UI (performance + DX). Don't try to force one architecture for both.

How FluxWriter handles headless integrations

While FluxWriter's primary integrations are WordPress, Shopify, Wix, and Webflow, headless integrations via webhook are possible:

Effective workflow if you're committed to headless: FluxWriter handles content generation + SEO meta + featured image; the webhook handler bridges to your CMS.

The summary

Headless CMS + Next.js gives developers complete frontend control but requires deliberate SEO implementation. The 12 essentials: SSR (not CSR), sitemap.xml generation, robots.txt, per-page meta tags, canonical URLs, OG/Twitter tags, JSON-LD schema, image optimization, internal linking, IndexNow integration, ISR for updates, Core Web Vitals optimization.

Each is a small task; collectively they're 2-5 days of development work to implement properly. The payoff: a faster site than WordPress can achieve, with the same SEO foundations.

Pick headless when you have dev resources and care about frontend performance. Stay on WordPress (or similar monolithic CMS) when your content team needs SEO automation without ongoing dev support.



← All posts