Auto-Generate sitemap.xml for Every Static Framework (Hugo, Astro, VitePress, Eleventy)
Unified sitemap checklist: lastmod, priority, image entries, and split sitemaps for 500+ posts.
Problem: Forgotten manual sitemap after migration. 40 new posts not indexed for 6 weeks. Fix: Auto-generate on every build. Zero maintenance.
Minimum viable sitemap fields
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://example.com/posts/core-web-vitals/</loc>
<lastmod>2026-05-01</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
</urlset>
lastmod must reflect real file change date — Google ignores fake daily lastmod.
Hugo
hugo.toml:
[sitemap]
changefreq = 'monthly'
priority = 0.5
[taxonomies]
# disable tag pages in sitemap if thin content
Exclude low-value pages:
{{- /* layouts/_default/sitemap.xml */ -}}
{{- range .Data.Pages -}}
{{- if and (not .Params.noindex) (ne .Kind "taxonomy") -}}
<url>...</url>
{{- end -}}
{{- end -}}
Set noindex: true on tag pages with < 3 posts.
Astro
npx astro add sitemap
// astro.config.mjs
import sitemap from '@astrojs/sitemap';
export default defineConfig({
site: 'https://example.com',
integrations: [sitemap({
filter: (page) => !page.includes('/tags/'),
serialize(item) {
if (item.url.includes('/posts/')) item.priority = 0.8;
return item;
},
})],
});
VitePress
No built-in sitemap. Post-build script:
// scripts/sitemap.mjs
import { SitemapStream, streamToPromise } from 'sitemap';
import { createWriteStream } from 'fs';
import { glob } from 'glob';
const links = (await glob('.vitepress/dist/**/*.html'))
.filter(f => !f.includes('404'))
.map(f => ({ url: f.replace('.vitepress/dist','').replace('/index.html','/'), changefreq: 'weekly' }));
const stream = new SitemapStream({ hostname: 'https://example.com' });
links.forEach(l => stream.write(l));
stream.end();
await streamToPromise(stream).then(data => createWriteStream('public/sitemap.xml').write(data));
robots.txt pointer
User-agent: *
Allow: /
Sitemap: https://example.com/sitemap.xml
Put in static/robots.txt (Hugo) or public/robots.txt (VitePress/Astro).
Split sitemaps (500+ URLs)
Google limit: 50,000 URLs / 50MB per file.
<!-- sitemap_index.xml -->
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap><loc>https://example.com/sitemap-posts.xml</loc></sitemap>
<sitemap><loc>https://example.com/sitemap-pages.xml</loc></sitemap>
</sitemapindex>
Submit once, verify forever
Search Console → Sitemaps → submit URL. CI check:
xmllint --noout public/sitemap.xml && echo OK
curl -s https://example.com/sitemap.xml | grep -c '<loc>'
Alert if URL count drops > 10% between deploys (broken generator).
Image sitemap (optional boost)
Add xmlns:image namespace for tutorial screenshots. See image SEO checklist post.
Results after fixing
Indexed pages: 312 → 398 (+27%)
Time to index new post: 14 days → 3 days
Crawl errors in GSC: 47 → 0
Sitemap is infrastructure. Automate it or forget it.