Fix Your Sitemap for Gatsby

Updated April 2026·By SitemapFixer Team

Gatsby emits sitemap-index.xml at build via gatsby-plugin-sitemap, but sitemap quality depends entirely on the GraphQL query, resolveSiteUrl, and exclude patterns you set in gatsby-config.js.

Analyze your Gatsby sitemap nowTry Sitemap Fixer Free

Gatsby builds its sitemap from the allSitePage GraphQL node, so any page not in allSitePage won't land in the sitemap. Any page you didn't want in there, but that got created by a source plugin, will. This guide covers what breaks at the node level and how to fix it.

Audited a Gatsby + Contentful site earlier this year with 2,400 published entries. The sitemap had 7,800 URLs. The culprit: draft entries from Contentful's preview API plus paginated tag archives the team had forgotten existed. Fixing the GraphQL filter dropped the sitemap to 2,390 URLs - closer to what actually deserves indexing.

gatsby-plugin-sitemap vs gatsby-plugin-advanced-sitemap

gatsby-plugin-sitemap is the official one. It produces one index plus sharded files, respects your siteUrl, and accepts query, resolvePages, and serialize hooks. Good default.

gatsby-plugin-advanced-sitemap is the Ghost fork. It splits into per-type sitemaps automatically (pages, posts, authors, tags) and includes image entries when your source plugin exposes them. Use it if you want typed sitemaps without writing the serialize logic yourself. The trade-off: slower release cadence and a query shape that assumes Ghost/Contentful conventions.

Common Gatsby Sitemap Issues

Working gatsby-config.js example

module.exports = {
  siteMetadata: {
    siteUrl: 'https://example.com',
  },
  plugins: [
    {
      resolve: 'gatsby-plugin-sitemap',
      options: {
        output: '/sitemap-index.xml',
        excludes: [
          '/dev-404-page',
          '/offline-plugin-app-shell-fallback',
          '/404',
          '/404.html',
          '/preview/*',
          '/admin/*',
        ],
        query: `{
          allSitePage(filter: {path: {regex: "/^(?!/draft)/"}}) {
            nodes { path }
          }
          allContentfulPost(filter: {node_locale: {eq: "en-US"}}) {
            nodes { slug updatedAt }
          }
        }`,
        resolveSiteUrl: () => 'https://example.com',
        resolvePages: ({ allSitePage, allContentfulPost }) => {
          const postMap = Object.fromEntries(
            allContentfulPost.nodes.map(p => [`/blog/${p.slug}/`, p.updatedAt])
          );
          return allSitePage.nodes.map(page => ({
            ...page,
            lastmod: postMap[page.path] || undefined,
          }));
        },
        serialize: ({ path, lastmod }) => ({
          url: path,
          lastmod,
          changefreq: 'weekly',
        }),
      },
    },
  ],
};

Handling i18n locales

gatsby-plugin-react-i18next (or gatsby-plugin-intl) creates one page node per locale prefix. The default sitemap lists all of them but without hreflang alternates, which means Google treats them as separate pages with duplicate content.

Use the serialize hook to emit an xhtml:link alternate array per URL, or generate one sitemap file per locale and reference them all from sitemap-index.xml. The per-locale approach is easier to debug: when French pages drop out of the index, you see which file they came from.

Client-only routes

Routes you created in gatsby-node.js with createPage({ matchPath: '/app/*' }) don't show up in allSitePage because they render fully client-side. If these routes need indexing (rare for /app/, common for /profile/[id]), use resolvePages to inject them manually from your data source. Most Gatsby /app/ dashboards should not be in the sitemap anyway.

Deployment pipeline notes

The sitemap runs in onPostBuild, which fires after HTML generation. If your CI pipeline starts the deploy before the build completes, you can ship HTML without the sitemap. Confirm your deploy step waits for gatsby build to exit cleanly, and include public/sitemap-*.xml in your artifact upload. On Gatsby Cloud and Netlify this is automatic; on custom pipelines it is the #1 reason "the sitemap didn't deploy".

Step-by-Step Fix Guide

  1. Run npm install gatsby-plugin-sitemap and add it to the plugins array
  2. Set siteMetadata.siteUrl to your production origin - the plugin prepends it to every path
  3. Add excludes for dev routes, 404, preview paths, and staging-only patterns
  4. Override query to filter source plugin nodes by publish status and locale
  5. Use resolvePages to merge client-only routes or inject real lastmod from your CMS
  6. Run gatsby build and inspect public/sitemap-index.xml plus each shard
  7. Verify with curl https://yoursite.com/sitemap-index.xml that the file is deployed and returns 200
  8. Submit sitemap-index.xml to Google Search Console and watch the per-shard coverage

Frequently Asked Questions

gatsby-plugin-sitemap vs gatsby-plugin-advanced-sitemap?
gatsby-plugin-sitemap is the default, maintained by the Gatsby team, and produces a sitemap index with sharded files automatically. gatsby-plugin-advanced-sitemap is Ghost's fork, tuned for Ghost/Contentful-style source plugins and gives per-type sitemaps. If you want grouped sitemaps out of the box, use advanced. Otherwise the official plugin is fine.
Why is my Gatsby sitemap missing client-only routes?
Because client-only routes defined with matchPath in gatsby-node.js do not have a page node in allSitePage. Use the resolvePages option to merge them in manually.
Where is my Gatsby sitemap after build?
In the public/ directory after gatsby build. The index is public/sitemap-index.xml and shards are public/sitemap-0.xml, public/sitemap-1.xml, etc. Confirm your build step uploads the entire public/ folder, not just HTML.
Analyze your Gatsby sitemap
Find all issues in your sitemap - free, no credit card needed
Analyze My Sitemap Free
Other platform guides