How to Do Technical SEO for Headless CMS and JAMstack Sites

TL;DR

Headless CMS and JAMstack architectures separate content management from content delivery, creating both opportunities and challenges for SEO. The core concern is rendering: how does Googlebot see your content when it’s built with JavaScript frameworks? Success requires: understanding rendering options (static, server-side, client-side), implementing proper pre-rendering for SEO-critical pages, configuring technical elements (meta tags, structured data, sitemaps) in a decoupled environment, and monitoring how Googlebot actually experiences your site. Done right, JAMstack can be SEO-superior through speed and reliability; done wrong, your content is invisible to search engines.


Do This Today (3 Quick Checks)

  1. Test what Googlebot sees: Use GSC URL Inspection → “View Crawled Page” → HTML tab. Is your content there, or just empty containers?
  1. Check rendering method: Is your site static (HTML at build), SSR (HTML at request), or CSR (HTML in browser)? This determines your SEO baseline.
  1. Verify meta tags render: Right-click → View Source on a page. Are title, meta description, and canonical in the initial HTML, or added by JavaScript?

Rendering Options Comparison

Rendering Type When HTML Generated SEO Implications Best For
<strong>Static (SSG)</strong> Build time Excellent – HTML ready for Googlebot Content that doesn't change often
<strong>Server-Side (SSR)</strong> Request time Excellent – HTML generated per request Dynamic content, personalization
<strong>Client-Side (CSR)</strong> Browser Risky – Googlebot must execute JavaScript Apps where SEO isn't critical
<strong>Incremental Static (ISR)</strong> Build + revalidation Excellent – Static with freshness Large sites with frequent updates

WordPress to Headless Migration SEO Checklist

Pre-Migration Preparation:

Task Details Tool
Export all URLs Complete URL list with traffic Screaming Frog + GA4
Map redirects Old URL → New URL for every page Spreadsheet
Document current SEO config Titles, metas, canonicals, schema Screaming Frog
Export backlink data URLs with external links Ahrefs
Benchmark rankings Top 50-100 keywords Rank tracker
Screenshot current SERPs Key branded and non-branded Manual

URL Structure Decisions:

WordPress Default Headless Options Recommendation
/blog/post-name/ /blog/post-name/ Keep identical if possible
/category/name/ /blog/category/name/ or /category/name/ Keep identical
/?p=123 N/A Redirect to clean URLs
/wp-content/uploads/ /images/ or CDN Redirect or maintain

Technical Element Migration:

Element WordPress Source Headless Implementation
Title tags Yoast/RankMath generateMetadata() or Head component
Meta descriptions Yoast/RankMath generateMetadata()
Canonical URLs Yoast/RankMath alternates.canonical in metadata
Open Graph Yoast/RankMath openGraph in metadata
Schema/JSON-LD Plugin or manual Script tag in component
XML Sitemap Plugin auto-generated app/sitemap.js or build script
Robots.txt Plugin or file app/robots.js or public/robots.txt
Hreflang Plugin Manual implementation per page

Migration Day Checklist:

Step Timing Verification
Deploy new site T-0 Site accessible
Implement all redirects T-0 Test sample redirects
Submit new sitemap to GSC T+1hr GSC shows submission
Request indexing for key pages T+1hr URL Inspection
Verify redirects working T+2hr Screaming Frog crawl
Check for 404s T+2hr GSC or crawl
Verify meta tags rendering T+2hr View source, GSC Inspection
Verify schema rendering T+2hr Rich Results Test
Monitor traffic T+24hr GA4 real-time

Post-Migration Monitoring:

Metric Tool Frequency Alert Threshold
Organic traffic GA4 Daily (first week) >20% drop
Crawl errors GSC Daily (first week) Any new errors
Index coverage GSC Daily (first week) Significant drops
Rankings Rank tracker Daily (first week) >5 position drops
Core Web Vitals GSC Weekly New "Poor" ratings

The Conversation

Kevin, CTO at a content startup, migrating from WordPress to a headless CMS with Next.js frontend, site has 500 pages with established organic traffic, concerned about maintaining SEO during and after migration, advanced technical knowledge but new to JAMstack SEO specifics: We’re migrating our content site from WordPress to a headless setup. Using Contentful as CMS and Next.js for the frontend. We have 500 pages getting good organic traffic. I want to make sure we don’t destroy our SEO. What do I need to know about SEO for headless and JAMstack?

SDC SEO Brain: Good news: Next.js is one of the better frameworks for SEO because it supports multiple rendering strategies. The critical question is: which rendering mode are you planning to use?

Kevin: We were thinking of using static generation for most pages since content doesn’t change that often.

SDC SEO Brain: Static generation (SSG) is SEO-ideal. HTML is generated at build time, so Googlebot gets fully-rendered pages immediately. No JavaScript execution required for them to see content. With SSG:

Advantages:

  • Fastest possible page load (pre-built HTML)
  • No rendering dependency for Googlebot
  • CDN-cached globally
  • Most reliable for SEO

Considerations:

  • Build time grows with page count
  • Content updates require rebuild
  • Dynamic content needs client-side handling

For 500 pages, build time should be manageable. Are any pages dynamic?

Kevin: Some pages show related content based on user behavior, and we have a search function.

SDC SEO Brain: Those dynamic elements can be client-side without hurting SEO. The core content should be static; interactive features layer on top.

Pattern:

// Next.js page with static core, dynamic additions
export async function getStaticProps() {
  const content = await getContentFromCMS();
  return { props: { content } };
}

export default function Page({ content }) {
  return (
    <>
      <article>{content.body}</article> {/* Static, SEO-visible */}
      <RelatedContent /> {/* Client-side, not critical for SEO */}
    </>
  );
}

Kevin: What about meta tags? Currently WordPress handles those automatically.

SDC SEO Brain: In headless setups, meta tags are your responsibility. With Next.js App Router:

// app/blog/[slug]/page.js
export async function generateMetadata({ params }) {
  const post = await getPost(params.slug);
  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      images: [post.featuredImage],
    },
    alternates: {
      canonical: `https://example.com/blog/${params.slug}`,
    },
  };
}

Verify they render:

  • View page source (not inspect element)
  • Meta tags must be in initial HTML
  • Test with GSC URL Inspection

Kevin: What about structured data?

SDC SEO Brain: Structured data should also be in initial HTML:

export default function Page({ content }) {
  const structuredData = {
    "@context": "https://schema.org",
    "@type": "Article",
    "headline": content.title,
    "datePublished": content.publishDate,
    "author": {
      "@type": "Person",
      "name": content.author.name
    }
  };

  return (
    <>
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
      />
      <article>{content.body}</article>
    </>
  );
}

Kevin: Sitemaps were automatic with WordPress. How do we handle that?

SDC SEO Brain: Generate sitemaps programmatically. Next.js App Router approach:

// app/sitemap.js
export default async function sitemap() {
  const posts = await getAllPosts();
  return posts.map(post => ({
    url: `https://example.com/blog/${post.slug}`,
    lastModified: post.updatedAt,
  }));
}

// app/robots.js
export default function robots() {
  return {
    rules: { userAgent: '*', allow: '/' },
    sitemap: 'https://example.com/sitemap.xml',
  };
}

Kevin: What about internal linking?

SDC SEO Brain: Internal links must be in the static HTML. Don’t rely on JavaScript-rendered links for SEO-important navigation.

Good (static link):

<Link href={`/blog/${relatedPost.slug}`}>{relatedPost.title}</Link>

Bad (client-side loaded):

// Links fetched and rendered only after JavaScript executes
useEffect(() => {
  fetchRelatedPosts().then(setRelatedPosts);
}, []);

For SEO-important links, include them in getStaticProps so they’re in initial HTML.

Kevin: How do I monitor that Googlebot is seeing everything correctly?

SDC SEO Brain: Ongoing monitoring:

Tool What to Check
<strong>GSC URL Inspection</strong> "View Crawled Page" → HTML tab shows rendered content
<strong>GSC Coverage</strong> No unexpected "Crawled – not indexed" for JS issues
<strong>Mobile-Friendly Test</strong> Shows rendered HTML
<strong>Rich Results Test</strong> Validates structured data visibility
<strong>Server logs</strong> Googlebot crawl patterns and response codes

Kevin: Any Next.js specific gotchas?

SDC SEO Brain: Next.js SEO gotchas:

Issue Solution
<strong>Trailing slashes inconsistency</strong> Configure in next.config.js
<strong>'use client' components</strong> Keep SEO content in server components
<strong>Image alt text</strong> Verify next/image includes alt
<strong>404 handling</strong> Ensure proper status code in not-found.js
<strong>Dynamic routes</strong> Use generateStaticParams for pre-rendering
<strong>Locale/i18n</strong> Configure properly for hreflang

FAQ

Q: Can Googlebot render JavaScript?
A: Yes, but with delays. Googlebot uses Chrome-based renderer, but there’s a “second wave” for JS content. Static/SSR is more reliable.

Q: Is client-side rendering ever okay for SEO?
A: For non-SEO-critical pages (dashboards, logged-in areas), CSR is fine. For pages you want indexed, pre-render them.

Q: What’s the SEO difference between SSG and SSR?
A: Both serve fully-rendered HTML, so SEO equivalent. SSG is faster (cached) but requires rebuilds. SSR is always fresh but has server load.

Q: Do I need to pre-render every page?
A: For SEO, pre-render pages you want indexed. Admin pages, authenticated areas don’t need it.


Summary

JAMstack can be SEO-excellent if rendering is handled correctly. The key is ensuring Googlebot sees content without executing JavaScript.

Rendering priority for SEO:

  1. Static Generation (SSG) – Best for most content
  2. Server-Side Rendering (SSR) – Best for dynamic content
  3. Incremental Static Regeneration (ISR) – Large, frequently updated sites
  4. Client-Side Rendering (CSR) – Avoid for SEO-critical pages

Critical elements in initial HTML:

  • Title and meta description
  • Canonical URL
  • Structured data (JSON-LD)
  • Main content
  • Internal navigation links

Programmatic requirements:

  • Generate sitemaps at build time
  • Configure robots.txt
  • Handle meta tags per-page
  • Implement structured data in templates

Monitoring is essential:

  • GSC URL Inspection for render verification
  • Coverage report for indexing issues
  • Build monitoring for failures

Sources