Setting Up Sitemap.xml Properly in Astro

How to generate, validate, and submit a sitemap.xml in Astro — including hreflang pairs, exclusion rules, and what Google actually does with it.

A correct sitemap is the cheapest SEO improvement you can ship. A wrong one quietly leaks pages or pollutes Google with broken URLs.

Background

Astro’s official @astrojs/sitemap integration covers 90% of cases out of the box. The remaining 10% — exclusions, alternate language pairs, last-modified dates — is where most sites get it wrong. Getting these right at setup time saves rounds of indexing problems later.

How to tell

  • Your site has more than 30 URLs you want indexed.
  • You have multiple languages or regional variants.
  • You ship draft, hidden, or auth-only pages that must stay out of search.
  • You want to give Google fresh lastmod signals when content changes.

Quick verdict

Use @astrojs/sitemap as the foundation. Customize via the filter and serialize options to handle exclusions and hreflang.

Step by step

  1. Install the official integration:
npx astro add sitemap
  1. Configure astro.config.mjssite: must be set or the integration silently produces a useless file. For a bilingual content site, this is the shape that works:
import { defineConfig } from 'astro/config';
import sitemap from '@astrojs/sitemap';

export default defineConfig({
  site: 'https://yourdomain.com',
  trailingSlash: 'always',
  build: { format: 'directory' },
  integrations: [
    sitemap({
      i18n: {
        defaultLocale: 'en',
        locales: { en: 'en', zh: 'zh-CN' },
      },
      filter: (page) =>
        !page.includes('/drafts/')
        && !page.includes('/preview/')
        && !page.includes('/admin/')
        && !page.endsWith('/404/'),
      serialize: (item) => {
        // Tag homepage as daily, articles as weekly, everything else monthly
        if (item.url.match(/\/articles\/[^/]+\/$/)) item.changefreq = 'weekly';
        else if (item.url === 'https://yourdomain.com/') item.changefreq = 'daily';
        else item.changefreq = 'monthly';
        return item;
      },
    }),
  ],
});
  1. Run the build and confirm the sitemap files exist:
npm run build
ls -la dist/sitemap*.xml
# dist/sitemap-index.xml
# dist/sitemap-0.xml
head -20 dist/sitemap-0.xml
  1. The generated XML should look like this — each URL pair carries xhtml:link alternates for hreflang:
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
        xmlns:xhtml="http://www.w3.org/1999/xhtml">
  <url>
    <loc>https://yourdomain.com/en/articles/astro-sitemap-setup/</loc>
    <xhtml:link rel="alternate" hreflang="en"
                href="https://yourdomain.com/en/articles/astro-sitemap-setup/" />
    <xhtml:link rel="alternate" hreflang="zh-CN"
                href="https://yourdomain.com/zh/articles/astro-sitemap-setup/" />
    <changefreq>weekly</changefreq>
  </url>
</urlset>
  1. Add a public/robots.txt that points to the sitemap so crawlers find it without manual steps:
User-agent: *
Allow: /

Disallow: /drafts/
Disallow: /preview/
Disallow: /admin/

Sitemap: https://yourdomain.com/sitemap-index.xml
  1. After deploy, fetch the sitemap and count URLs — quick sanity check against your article count:
curl -s https://yourdomain.com/sitemap-index.xml | grep -c '<loc>'
curl -s https://yourdomain.com/sitemap-0.xml | grep -c '<loc>'
# should roughly equal (en articles + zh articles + hub pages)
  1. Submit sitemap-index.xml once in Search Console → Sitemaps. After that, you never resubmit unless the URL changes — Google polls it on its own.

Common pitfalls

  • Forgetting to set site: in config — Astro silently produces a useless sitemap.
  • Listing pages that return 404 or redirect — every wrong URL chips at your crawl budget.
  • Including draft or test URLs and watching them get indexed before you notice.
  • Setting lastmod to the build date for every page — Google ignores it as noise.
  • Submitting one sitemap per language separately when a sitemap index with hreflang would be cleaner.

Who this is for

Any Astro site that depends on organic search.

When to skip this

Private internal tools or single-page sites where a sitemap adds noise without benefit.

FAQ

  • Does Astro generate a sitemap automatically?: Only if you add the @astrojs/sitemap integration. Stock Astro does not.
  • How big can a sitemap be?: A single sitemap file can hold up to 50,000 URLs or 50 MB. The Astro integration splits automatically with a sitemap index.
  • Do I need to ping Google when the sitemap changes?: No, that endpoint was deprecated. Just keep the sitemap fresh and let Google recrawl on its own schedule.
  • What if some pages should not be in the sitemap?: Exclude them via the filter option and also add noindex to the page itself. Belt and suspenders.

Tags: #Indie dev #Astro #SEO #Technical SEO #Indexing