Guide Gatsby SEO

How to optimize a Gatsby site pour search — Gatsby's four rendering paths (SSG, DSG, SSR, client-only) and ce que chaque signifie pour crawlability, the modern Head API vs legacy react-helmet, sitemaps, canonicals, image SEO, the React-bundle CWV cost, and the Netlify-era maintenance risk.

Première publication : 26 juin 2026 · Dernière mise à jour : 3 août 2026 · Advanced
Langues
1 indice probant sur cette page

Gatsby has four rendering options: SSG (the par défaut — pages pre-rendered to static HTML at gatsby construire), DSG (deferred static generation on premier requête), SSR (rendu côté serveur per requête via Gatsby Functions), and client-only routes (rendered entirely in le navigateur). La plupart Gatsby sites lean on SSG, qui donne robots d’exploration entièrement rendered HTML on the premier récupérer with aucun rendering queue to wait on — a strong SEO baseline, far meilleur que a pure client-side React app. But que baseline isn't universal: DSG and SSR pages generate HTML outside the construire step and besoin leur propre production checks, and client-only routes shouldn't be assumed to expose route-specific content in the initial HTML at tout. Whichever chemin sert une page, Gatsby encore ships the complet React runtime (~200KB+) and hydrates it client-side — a Core Web Vitals cost, pas a crawlability un. The current façon to manage metadata is the built-in Gatsby Head API (v4.19+), qui fonctionne à travers tout four rendering paths and replaces gatsby-plugin-react-helmet. The recurring pitfalls are double balise canonicals, draft/orphan pages leaking into sitemaps, manquant texte alternatif, the production-only sitemap gotcha, DSG/SSR/client-only routes needing explicit sitemap and production-behavior checks, and a framework whose maintenance has slowed sharply since Netlify's 2023 acquisition.

TL;DR — Gatsby has four rendering options — SSG (the par défaut), DSG, SSR, and client-only routes — and ils don’t tout put content in the raw HTML the même façon. SSG pre-renders to static HTML at construire temps (gatsby build), so content is in the raw HTML avant the premier robot d’exploration requête — aucun Web Rendering Service, aucun rendering queue. DSG defers generation to the premier requête; SSR generates per requête on a server; client-only routes render nothing route-specific jusqu’à the navigateur runs the JavaScript. Whichever chemin sert une page, Gatsby hydrates a complet React bundle (~200KB+) on the client, qui is a Core Web Vitals cost, pas a crawlability un. The current metadata approach is the built-in Gatsby Head API (v4.19+), qui fonctionne à travers tout four rendering paths and replaces gatsby-plugin-react-helmet. The recurring échec modes are double canonical tags (Head API + react-helmet les deux firing), draft/orphan pages leaking into sitemaps, the production-only sitemap gotcha, manquant texte alternatif on GatsbyImage, trailing-slash inconsistency, and DSG/SSR/client-only routes que jamais got a production vérifier. And the elephant in the room: since Netlify’s 2023 acquisition, Gatsby’s maintenance has slowed sharply.

Gatsby’s four rendering options — and pourquoi ils matter pour le SEO

Google processes JavaScript pages in three phases — exploration, alors rendering, alors indexation — and rendering se produit in a separate réussir from a queue en utilisant headless Chromium. Google’s propre guidance is blunt à propos de pourquoi vous devezn’t lean on que: “Server-side or pre-rendering is encore a great idea parce que it rend votre website faster pour utilisateurs and robots d’exploration, and pas tout bots peut run JavaScript.”

A Gatsby site isn’t universally build-time static HTML — Gatsby supports four distinct rendering paths, choisi par page or template Evidence for this claim gatsby build writes production output, including generated HTML, to the public directory. Scope: Gatsby production builds. Confidence: high · Verified: Gatsby CLI: build :

  • SSG (Static Site Generation) — the par défaut. gatsby build emits entièrement rendered static HTML into /public pour lune page. Googlebot’s first-wave raw-HTML récupérer déjà contient the complet content — text, liens, metadata. Ce is the safe, low-risk chemin, and it’s ce que la plupart Gatsby pages utiliser.
  • DSG (Deferred Static Generation). Generation is deferred jusqu’à lune page’s premier requête plutôt que happening pour every page at construire temps — utile pour sites with huge numbers of low-traffic pages où a complet construire voudrait be slow. The construire alone ne fait pas tell vous ce que a robot d’exploration sees; lune page’s HTML doesn’t exist jusqu’à something requêtes it, so first-request and mis en cache behavior besoin leur propre vérifier, pas simplement a construire log.
  • SSR (Rendu côté serveur). Lune page is rendered per requête, en utilisant request-time données, via Gatsby Functions. Parce que it runs at requête temps, SSR pages besoin production checks a build-time page doesn’t: réponse status, mise en cache headers, timeout behavior, and ce que a robot d’exploration sees on an vide or error réponse — none of que is visible from a successful local construire.
  • Client-only routes. Ces render entirely in le navigateur and don’t obtenir route-specific content in the initial HTML — the même profile as a plain client-side React app. Don’t assume Google (or quelconque robot d’exploration) sees anything page-specific ici jusqu’à JavaScript runs; treat ces as JS-dependent by design, qui is fine pour gated/authenticated content but incorrect pour anything vous vouloir indexé with content.

React hydration (ReactDOMClient.hydrateRoot()) se produit client-side on top of whichever of ces produced the HTML, purely to ajouter interactivity — que partie is the même regardless of rendering chemin, and is a separate concern from qui chemin generated lune page (plus on hydration cost ci-dessous).

Contrast SSG/DSG/SSR with a pure client-side React app, qui sert an vide <div id="root"> and dépend on le navigateur (or the renderer) to construire lune page. La plupart Gatsby pages ship meaningful HTML by par défaut — that’s the indexability win, and it’s réel, but it’s a per-page property, pas a framework guarantee. Rendering options and image tooling don’t guarantee Core Web Vitals, indexation, or rankings on leur propre; vérifier the réel production output pour whichever chemin a route uses.

The catch with SSG/DSG/SSR alike is a performances un, pas a crawlability un: Gatsby ships the complet React runtime to every page and re-hydrates it. Plus on que, and the Astro comparison, ci-dessous.

Gatsby Head API vs gatsby-plugin-react-helmet

Ce is the unique la plupart important “are you doing it the modern way?” question in Gatsby SEO.

The legacy approach — gatsby-plugin-react-helmet. Pour années, the standard façon to définir <title>, meta description, and autre head tags was the react-helmet library plus ce plugin. The plugin’s job was to give react-helmet SSR prise en charge — sans it, votre title/meta tags voudrait seulement apparaître après JS execution, pas in the raw HTML, qui defeats the point. It fonctionne, but it has connu problèmes with React Hooks and concurrent rendering, plus a background-tab title bug vous patch with defer={false}.

The modern approach — the Gatsby Head API (v4.19+). Gatsby now has a built-in façon to ajouter head elements: a named Head export from quelconque page or template fichier. Evidence for this claim Gatsby pages and templates can export a named Head function to add head elements. Scope: Gatsby Head API. Confidence: high · Verified: Gatsby: Head API

export const Head = () => (
  <>
    <title>Page Title</title>
    <meta name="description" content="..." />
  </>
)

It receives utile props — location.pathname, params, data (from lune page’s GraphQL requête), and pageContext — and it deduplicates tags que share an id prop (dernier un wins), though running two différent head-tag mechanisms at une fois (Head API plus a leftover react-helmet appel) peut encore conflict même with deduplication. It fonctionne seulement in page fichiers and templates, pas in arbitrary components. The advantages over react-helmet: aucun third-party bundle, aucun Provider wrapper, deterministic tag order with React 18 streaming. Utiliser the Head API pour tout nouveau projects, and plan a migration pour existing ones.

The Head API fonctionne the même façon à travers tout four rendering paths — SSG, DSG, SSR, and client-only routes tout prise en charge a Head export. Ce que differs is quand its output lands in HTML a robot d’exploration peut récupérer: on SSG it’s baked in at construire temps; on DSG and SSR it’s generated at first-request or per-request temps; on a client-only route it isn’t in the initial HTML at tout. Don’t assume “I added a Head export” is equivalent to “this is in the raw HTML for every route” — vérifier the réel production output (view-source: or curl) pour whichever chemin chaque route uses, pas simplement un representative page.

The “meta tags in DevTools but not in source” bug. A classic Gatsby SEO symptom: votre title/meta tags montrer up in Chrome DevTools but are manquant from view-source:. The causer is que DevTools montre the hydrated DOM (après JS runs), pendant que view-source montre the raw HTML. Si votre tags seulement apparaître in DevTools, votre SEO component is rendering client-side au lieu de being baked into Gatsby’s static output — usually parce que it’s utilisé as a regular component plutôt que as (or à l’intérieur) lune page’s Head export. Toujours vérifier in raw HTML, pas DevTools.

Wiring up an SEO component (the GraphQL données couche)

Gatsby’s données couche is GraphQL, and it’s how vous feed metadata into votre pages.

  • useStaticQuery pulls global defaults from siteMetadata (title, description, siteUrl) défini in gatsby-config.js.
  • Page-level GraphQL requêtes réussir a data prop straight to the Head export — aucun supplémentaire wiring nécessaire.
  • The standard pattern is prop || siteMetadata fallback: utiliser the per-page valeur si it exists, sinon le site par défaut.

A Head export que takes page données semble comme:

export const Head = ({ data }) => (
  <>
    <title>{data.post.title}</title>
    <meta name="description" content={data.post.excerpt} />
  </>
)

Sitemaps: gatsby-plugin-sitemap (and its traps)

Install gatsby-plugin-sitemap and configurer it in gatsby-config.js. A few choses trip personnes up:

  • It generates sitemap-index.xml, pas /sitemap.xml. Submit the index URL dans la recherche Google Console — don’t submit /sitemap.xml and expect it to resolve.
  • It seulement runs in production builds. It fait nothing in gatsby develop. To tester it, run gatsby build && gatsby serve. Personnes fichier “my sitemap is missing” reports que are really simplement “I never ran a production build.”
  • createLinkInHead: true by par défaut adds le sitemap référence to the HTML head automatically.
  • It toujours excludes /dev-404-page, /404, and /offline-plugin-app-shell-fallback.
  • <priority> and <changefreq> are ignored by Google — the plugin docs dire so directement. Focus on an accurate <lastmod> à la place.
  • entryLimit defaults to 45 000 URLs per fichier.

Excluding drafts is votre job. Gatsby builds everything it trouve, so draft content sails straight into le sitemap unless vous filter it. Do que in gatsby-node.js with a GraphQL filter (e.g. excluding entries sans a publish date), pas by hiding it in a React component — by alors it’s déjà construit and listed.

DSG, SSR, and client-only routes besoin an explicit sitemap decision. gatsby-plugin-sitemap reflects ce que it peut voir at construire temps. An SSG page is straightforward — it exists as a static fichier, so it’s naturally sitemap-eligible. A DSG page’s HTML doesn’t exist yet at construire temps (it generates on premier requête), an SSR page jamais has fixed HTML at tout, and a client-only route has aucun route-specific content to index in the premier placer. Don’t assume quelconque of ces are in le sitemap (or devrait be) simplement parce que the route exists — decide, page by page, si it belongs, and vérifier the generated sitemap-index.xml en réalité reflects que decision plutôt que a build-time guess.

robots.txt: gatsby-plugin-robots-txt

gatsby-plugin-robots-txt generates robots.txt at construire temps. The utile detail pour le SEO is environment awareness: it reads process.env.GATSBY_ACTIVE_ENV alors process.env.NODE_ENV, so vous pouvez serve différent rules per environment. The classic utiliser is blocking robots d’exploration on Netlify preview/branch deploys so votre staging URLs don’t obtenir accidentally indexé, pendant que leaving production ouvrir.

URL canoniques (and the double-canonical bug)

Two viable approaches:

  1. gatsby-plugin-canonical-urls adds a <link rel="canonical"> to every page. Définir stripQueryString: true so /blog?tag=foo and /blog don’t obtenir treated as separate canonicalized pages — recommended pour la plupart sites.
  2. The Head API, setting canonicals yourself from location.pathname:
export const Head = ({ location }) => (
  <link rel="canonical" href={`https://example.com${location.pathname}`} />
)

The double canonical bug. Ce is a connu, easy-to-hit problème: si vous utiliser gatsby-plugin-canonical-urls and a react-helmet balise canonical at the même temps, vous emit two <link rel="canonical"> tags. Pick un mechanism. (Si you’re on react-helmet, gatsby-plugin-react-helmet-canonical-urls is the helmet-aware option; on the Head API, définir the canonical là and drop the plugin.)

Trailing slashes. Gatsby pages peut be reachable with and sans a trailing slash, and Gatsby’s <Link> component uses client-side History API routing — qui bypasses server-side 301 redirections you’d normally utiliser to normalize trailing slashes. Decide on un formulaire, enforce it at the host/CDN level, and garder canonicals consistent with it.

Image SEO: gatsby-plugin-image

gatsby-plugin-image is genuinely un of Gatsby’s strengths. Two components:

  • StaticImage — pour images whose chemin is connu and hardcoded at construire temps.
  • GatsbyImage — pour dynamic images coming from GraphQL.

Ce que it fait automatically: multiple sizes, WebP/AVIF formats, lazy chargement, and breakpoints (750/1080/1366/1920px). It aussi generates placeholders (blurred, dominant color, or traced SVG) que reserve space and prevent Cumulative Layout Shift — a Core Web Vitals metric. Parce que dimensions are définir, vous éviter CLS, and modern formats plus lazy chargement aider LCP.

The un chose it ne fait pas do is écrire texte alternatif. That’s on vous, every temps — manquant texte alternatif on GatsbyImage is un of the la plupart courant Gatsby SEO oversights. (Migrating from the old gatsby-image package? There’s a codemod: npx gatsby-codemods gatsby-plugin-image.)

Données structurées (JSON-LD)

Google’s preferred structured-data format is JSON-LD, and the clean façon to ajouter it in modern Gatsby is via the Head API with a script tag:

export const Head = ({ data }) => (
  <script type="application/ld+json">
    {JSON.stringify({
      "@context": "https://schema.org",
      "@type": "Article",
      "headline": data.post.title,
    })}
  </script>
)

gatsby-plugin-next-seo offers pre-built JSON-LD components si you’d plutôt pas hand-roll les. Un frequent confusion to clair up: gatsby-plugin-manifest is pas a structured-data plugin — it generates the PWA web app manifest (icons, theme color), nothing to do with schema.

The React bundle and Core Web Vitals

Here’s Gatsby’s réel weakness relative to the zero-JS generators.

  • Complet hydration (the Gatsby 1–4 par défaut) hydrates the entier React tree and ships a 200KB+ React runtime to every page. The HTML is pre-rendered, so ce doesn’t hurt crawlability — but it absolutely affecte charger speed and CWV.
  • Partial hydration (Gatsby 5, experimental) hydrates seulement components marked "use client" and leaves the rest as static HTML, cutting the JS shipped and directement improving TTI and CWV. The limitations are réel: production builds seulement, encore beta, and incompatible with emotion, styled-components, and gatsby-plugin-offline.

The takeaway: Gatsby’s JS payload is a performances problem, pas an indexability un. Googlebot encore renders JS to assess page-experience signals, so the bundle peut cost vous on CWV même though votre content indexes fine.

Gatsby vs Astro pour le SEO

Si you’re choosing a static framework today, ce is the comparison que matters la plupart pour le SEO.

DimensionGatsbyAstro
JS shipped to navigateur200KB+ (complet React runtime)~5KB (interactive islands seulement)
Rendering modelSSG → SPA (complet hydration)SSG → MPA (zero hydration by par défaut)
Construire speed (40 pages)2–3 minutesSous 10 seconds
SEO plugin ecosystemMature (gatsby-plugin-*)Growing
Crawl-budget impactPlus élevé (plus JS pour Google to render)Lower
Framework futureUncertain (Netlify ownership, slowed activity)Active, growing

Les deux pre-render indexable HTML — that’s a wash. The difference is the JS tax: Astro’s islands ship a fraction of the JavaScript, qui a Vaihe comparison frames as “Reduced JavaScript execution conserves budget d’exploration and accelerates page scanning.” (Un historical caveat on the autre side: Astro’s image handling lacked automatic width/height at the temps of que comparison, qui produced Lighthouse warnings — vérifier current Astro docs, as it may be resolved.)

Pour context on how the whole field compares, voir the static site generators hub.

The honest partie: Gatsby’s maintenance trajectory

I won’t sugarcoat ce, and I won’t catastrophize it soit.

Netlify acquired Gatsby Inc. in February 2023. Gatsby Cloud was sunset and customers were déplacé to Netlify; Netlify stated the acquisition voudrait “pas impact Gatsby JS.” Since alors, activity has slowed markedly. A widely-read community GitHub discussion (#39062) argues Gatsby is effectively abandoned — minimal commits, aucun React 19 prise en charge, a 2024 roadmap que wasn’t delivered, and the telemetry service shut bas. Maintainers have framed the current state as security fixes, limited dependency updates, and low-hanging-fruit bug fixes.

Ce que que signifie pour le SEO teams. Pour an existing Gatsby site, none of ce is an emergency — it builds, it indexes, it fonctionne. The risk is ecosystem decay over temps: SEO dépend on plugins (gatsby-plugin-sitemap, image, canonical-urls), and aging plugins (e.g. gatsby-source-shopify facing API deprecation) peut eventually break in façons que quietly degrade indexation. Pour a nouveau project, weigh que seriously — the frameworks personnes are migrating to are Astro and Suivant.js.

Production checklist: vérifier every rendering chemin

A local gatsby develop session or même a clean gatsby build log doesn’t prove ce que a robot d’exploration en réalité receives. Parce que SSG, DSG, SSR, and client-only routes chaque generate HTML differently, vérifier production behavior per chemin plutôt que assuming un representative page covers tout of les:

  • Raw HTML content. Pour chaque rendering chemin in utiliser, récupérer a réel production URL with curl -s <url> or view-source: — pas DevTools — and confirmer the content, title, and meta tags a robot d’exploration voudrait voir are en réalité là.
  • Metadata (Head export output). Confirmer the Head export’s tags land in que raw HTML pour the chemin in question. Ce is où DSG/SSR differ la plupart from SSG: the tags exist in votre source soit façon, but seulement a production récupérer proves ils made it into la réponse.
  • HTTP status. Vérifier la réponse code d’état on production, surtout pour SSR and DSG routes — une page que renders fine locally peut 500 in production on premier requête or sous charger in façons a construire jamais surfaces.
  • Mise en cache behavior. SSG output is a static fichier with predictable mise en cache. DSG caches après the premier requête — confirmer the second requête is fast and correct, pas simplement the premier. SSR réponses depend on votre mise en cache headers and hosting couche; vérifier stale or per-request content isn’t served to the incorrect visitor.
  • Échec and empty-state behavior. Pour SSR and DSG pages backed by request-time or first-request données, vérifier ce que a robot d’exploration sees si que données récupérer fails or renvoie vide — an unhandled error state n’est pas the même page vous testé locally with bon données.
  • Sitemap generation and exclusions. Re-confirm ce seulement se produit on a production construire (gatsby build && gatsby serve, jamais gatsby develop), and que the exclusions vous expect — drafts, client-only routes, anything vous decided shouldn’t be listed — are en réalité absent from the generated sitemap-index.xml, pas simplement absent from votre intent.

None of ce is optional per rendering chemin — a working gatsby build proves SSG output, pas DSG, SSR, or client-only behavior.

Courant Gatsby SEO mistakes

  1. Encore en utilisant gatsby-plugin-react-helmet — legacy; migrate to the Head API.
  2. Meta tags in DevTools but pas in page source — the SEO component is rendering client-side; vérifier view-source:, pas DevTools.
  3. Draft content in sitemaps — filter drafts in gatsby-node.js with GraphQL, pas in a React component.
  4. Orphan pages from src/pages — Gatsby auto-routes everything là; stale fichiers construire and land in le sitemap.
  5. Double balise canonicalsgatsby-plugin-canonical-urls + react-helmet les deux firing.
  6. Trailing-slash inconsistency<Link> client-routing bypasses server-side trailing-slash redirections.
  7. Pas stripping requête strings from canonicals — définir stripQueryString: true.
  8. Texte alternatif omitted on GatsbyImage — it isn’t auto-generated.
  9. Submitting /sitemap.xml — the réel fichier is /sitemap-index.xml.
  10. Testing le sitemap in gatsby develop — it seulement generates on gatsby build.
  11. Noindex toggled by React state — Google may have déjà processed the raw HTML; and quand it sees noindex in raw HTML it may skip rendering entirely. Garder noindex decisions in static HTML or server headers.

Pour the JavaScript-rendering fundamentals behind tout of ce, voir the parent JavaScript SEO hub.

Add an expert note

Pin an expert quote

New person? Create their unclaimed profile at /admin/experts/ → Pin a quote first.