Gatsby SEO: static HTML, the Head API, and what to watch

You picked Gatsby for speed and a React workflow. Search sees your static HTML, then the page hydrates with JavaScript. This guide shows how to ship clean metadata, a working sitemap and fast images, what to watch in Core Web Vitals, and where teams migrate when maintenance bites.

By , founder of Porteur · Updated 14 September 2026 · Markdown

What good Gatsby SEO looks like today

You render static HTML that crawlers can read, then hydrate on the client. That is fine for ranking, if the static HTML carries your real content and metadata.

  • Set titles, descriptions, canonicals and social tags with the Head API, not in client-only effects.
  • Ship a sitemap with gatsby-plugin-sitemap and submit it in Search Console.
  • Optimise images with gatsby-plugin-image so LCP sits at or under 2.5 s and CLS stays at or under 0.1.
  • Keep hydration light so INP stays under 200 ms on key pages like / and /pricing.
  • Plan for maintenance: Gatsby’s pace has slowed since 2023, so check plugin health and consider migration paths.

Titles, descriptions and canonicals with the Head API

Use the Head API to declare metadata at build time. Older sites often used react-helmet. On a new or updated site, prefer Head so your defaults live in layouts and templates.

// src/pages/pricing.js
import * as React from "react"

export default function PricingPage() {
  return (
    <main>
      <h1>Pricing</h1>
      {/* page content */}
    </main>
  )
}

export function Head() {
  const title = "Pricing for YourProduct"
  const description = "Plans for startups and teams. Cancel any time."
  const canonical = "https://yourproduct.com/pricing"
  const ogImage = "https://yourproduct.com/og/pricing.png"

  return (
    <>
      <title>{title}</title>
      <meta name="description" content={description} />
      <link rel="canonical" href={canonical} />
      <meta property="og:title" content={title} />
      <meta property="og:description" content={description} />
      <meta property="og:image" content={ogImage} />
      <meta name="twitter:card" content="summary_large_image" />
    </>
  )
}

Set a default Head in your root layout and override per page. Keep titles under about 60 characters so results do not truncate. Write descriptions between 120 and 155 characters to fit most snippets.

  1. Put shared tags in a Layout Head

    Add site name, default og:image and a fallback description. Pull per-page values with props or page queries.

  2. Set a canonical on every indexable page

    Point to the preferred URL, for example https://yourproduct.com/guides/getting-started. This avoids duplicate URLs splitting signals.

  3. Avoid client-only metadata

    Do not set titles in useEffect. Crawlers read your HTML. Put everything important in Head.

The sitemap with gatsby-plugin-sitemap

Generate a sitemap so Search can discover pages fast. Gatsby does not ship one by default. Use gatsby-plugin-sitemap and check its output after every deploy.

// gatsby-config.js
module.exports = {
  siteMetadata: {
    siteUrl: "https://yourproduct.com",
  },
  plugins: [
    {
      resolve: "gatsby-plugin-sitemap",
      options: {
        excludes: ["/drafts/*", "/private/*"],
        createLinkInHead: true,
      },
    },
  ],
}
  1. Set siteUrl

    Put the canonical origin in siteMetadata. Use https and your chosen www or non-www form.

  2. Exclude non-indexable paths

    If you ship drafts under /drafts/, exclude them. Also add noindex in Head on those pages.

  3. Verify in Search Console

    Submit /sitemap-index.xml in the Sitemaps report. Fix path errors. Resubmit after major URL changes.

A correct setup will output a sitemap index and child sitemaps for pages. Your /pricing and /guides/getting-started should appear with the right lastmod dates after a content update.

Images with gatsby-plugin-image: LCP, CLS and size hints

Images decide speed and layout stability. Use gatsby-plugin-image to transform and serve responsive images. It helps Largest Contentful Paint and avoids layout shift with fixed dimensions.

// Hero image in a page component
import { StaticImage } from "gatsby-plugin-image"

export default function Home() {
  return (
    <header>
      <h1>Build docs people read</h1>
      <StaticImage
        src="../images/hero.png"
        alt="Dashboard screenshot"
        width={1200}
        height={630}
        placeholder="blurred"
        formats={["avif", "webp", "png"]}
        priority
      />
    </header>
  )
}
  • Set width and height to give the browser exact size. This keeps CLS at or under 0.1.
  • Use priority on the LCP image above the fold, for example the hero on /.
  • Serve modern formats like AVIF and WebP. Let the plugin generate multiple sizes.
  • Use GatsbyImage with GraphQL for CMS images inside templates.
  • Avoid giant social images in the markup outside of the Head. Put og:image only in metadata.

Check PageSpeed Insights for field data. Good LCP is up to 2.5 seconds. If LCP is an image, trim bytes and avoid blocking scripts above it. Lazy load below-the-fold images only, not the hero.

Hydration weight and Interaction to Next Paint

Gatsby ships static HTML. Then it hydrates with JavaScript. Heavy hydration penalises Interaction to Next Paint. Keep client-side work lean on landing pages you want to rank and convert.

  • Remove interactive widgets on content pages that do not need them: carousels, heavy chat launchers, complex animations.
  • Avoid rendering core content only on the client. Static HTML should contain headings, copy and links.
  • Audit third-party scripts. Defer marketing tags and cut unused ones. Check their impact on INP in PageSpeed Insights.
  • Prefer simple components for navigation and accordions. Keep event handlers small and fast.
  • Test on a mid-range phone. Lighthouse simulates one run, results vary. Rerun a few times and focus on Core Web Vitals in the field.

Aim for good thresholds as of 2026: INP up to 200 ms, LCP up to 2.5 s, CLS up to 0.1. Field assessment uses the 75th percentile over 28 days. You will see those in the Core Web Vitals report in Search Console when there is enough traffic.

Structured data and social previews

Add JSON-LD in the Head API for pages that can earn rich results. Keep it minimal and correct. Check your output in the Rich Results Test before shipping.

// Example: SoftwareApplication schema on /pricing
export function Head() {
  const jsonLd = {
    "@context": "https://schema.org",
    "@type": "SoftwareApplication",
    name: "YourProduct",
    applicationCategory: "BusinessApplication",
    operatingSystem: "Web",
    offers: {
      "@type": "Offer",
      priceCurrency: "USD",
      // Use your real prices here
    },
  }

  return (
    <>
      <title>Pricing for YourProduct</title>
      <meta name="description" content="Plans for startups and teams." />
      <script type="application/ld+json">{JSON.stringify(jsonLd)}</script>
      <meta property="og:title" content="Pricing for YourProduct" />
      <meta name="twitter:card" content="summary_large_image" />
    </>
  )
}

For articles, use Article markup on posts. For your brand page, add Organization markup. Keep one canonical per page and ensure the structured data matches visible content and metadata.

Maintenance has slowed: what to watch and when to migrate

Gatsby maintenance has slowed since 2023. That does not break your SEO by itself. It affects how fast bugs and integrations move. Treat plugin choice and updates with care as of 2026.

  • Check the health of key plugins: sitemap and image. Read their repositories and changelogs before you upgrade.
  • Keep Node and dependencies in a version set your host supports. Pin versions for stability.
  • Test builds on a staging URL with noindex. Crawl the stage for broken links and missing metadata before a release.
  • If team skills have shifted to another framework, plan a migration with URL parity and redirects.

Migration paths teams take from Gatsby

Most teams move for ecosystem and hiring reasons, not for pure SEO. The common paths keep React or go more static. Keep your URLs and metadata identical. Ship 301 redirects when they must change.

Move toMetadataSitemap and robotsImagesWatch for
Next.jsUse the Metadata API for titles, descriptions and canonicals. Map your Gatsby Head to layout.tsx and page files.Use sitemap.ts and robots.ts. Keep the same site URL and paths.Use next/image for responsive images.Avoid heavy client components on content pages. Static rendering by default helps.
AstroSet titles and metadata in layouts. No built-in SEO layer, you write the head as you do in Gatsby.Use @astrojs/sitemap. Replace or extend the automatic sitemap as needed.Built-in image optimisation with zero JavaScript by default.Pages ship zero JS unless added, which improves INP. Keep URL parity.
DocusaurusFront matter for title and description on docs. Good for product docs and guides.Use the sitemap plugin. Versioned docs need careful canonicals or noindex on old versions.Use its image pipeline inside the theme.Avoid duplicate pages across doc versions. Canonicalise the current.
  1. Freeze the Gatsby site

    Generate a list of all indexable URLs, titles, descriptions and canonicals. Export your sitemap and crawl it.

  2. Build URL parity first

    Match slugs and trailing slashes. Keep /guides/getting-started as /guides/getting-started.

  3. Carry over metadata

    Port Head tags one for one. Verify social tags and structured data.

  4. Ship redirects

    For changed paths, add 301s. Test in bulk. Keep them long term.

  5. Verify in Search Console

    Submit the new sitemap. Watch the performance and Core Web Vitals reports for shifts over the next 28 days.

Checks before and after you ship

  • Page-level: title under about 60 characters, description under 155, one canonical, primary h1, real content in static HTML.
  • Site-level: sitemap index resolves, includes only indexable pages, no 404s inside it.
  • Links: no orphan pages you want to rank. Internal links use the real final URL, not tracking parameters.
  • Speed: LCP up to 2.5 s, CLS up to 0.1, INP up to 200 ms on /, /pricing, and top guides.
  • Search Console: sitemap submitted, no spikes in warnings, URL Inspection returns the expected canonical.

A fixed page looks like this: /pricing has a unique title and description in Head, a canonical to https://yourproduct.com/pricing, the hero image as LCP with priority, and the page loads with stable layout and fast input response on a mid-range phone on throttled 4G in Lighthouse.

Troubleshooting: pages shown but never clicked

If impressions rise but clicks do not, your snippet is not pulling its weight or the intent is off. Start with the queries in the Search Console performance report and fix the page they map to.

  • Rewrite the title to match the query, for example change “Pricing” to “YourProduct pricing and plans”.
  • Tighten the description to match the benefit in one line, for example “Ship guides, search logs and dashboards. Plans for startups and teams.”
  • Add an internal link from a higher traffic page to push a relevant page up a level.
  • Avoid cannibalisation: do not run two guides on the same query. Merge and redirect the weaker one.

Questions

Sources

Check my site, free

Paste your homepage URL to get a free 30-second read on your pages, the searches around them and the rivals on them, with three findings shown in full.

  • Free check, no card
  • Read-only, your own accounts
  • Readable by your agent

Read next