Fix Your Sitemap for Nuxt
Nuxt 3 uses the @nuxtjs/sitemap module to generate sitemap.xml. Dynamic route params, server routes, and @nuxtjs/i18n integration often need explicit configuration or the sitemap ships with only the static pages.
Nuxt 3 has a really solid sitemap story via @nuxtjs/sitemap - better than what's in Next.js core, honestly. But it assumes you'll wire up your content source. The module can see pages/*.vue automatically. It can't see your Nuxt Content markdown files, your Strapi API, or your Supabase rows unless you tell it.
Reviewed a Nuxt 3 marketing site with 2,100 blog posts stored in Nuxt Content. The sitemap listed 8 URLs - just the static pages. The dev had installed @nuxtjs/sitemap, pointed site.url at production, and shipped. Nobody had written the sources config, so dynamic routes were invisible. A 20-line server/api/__sitemap__/urls.ts fixed it.
Working nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxtjs/sitemap', '@nuxtjs/i18n'],
site: {
url: 'https://example.com',
},
sitemap: {
sources: ['/api/__sitemap__/urls'],
exclude: ['/admin/**', '/dashboard/**', '/preview/**'],
autoI18n: true,
defaults: {
changefreq: 'weekly',
priority: 0.7,
},
},
routeRules: {
'/admin/**': { robots: 'noindex' },
'/api/**': { robots: 'noindex' },
},
i18n: {
locales: [
{ code: 'en', language: 'en-US' },
{ code: 'de', language: 'de-DE' },
],
defaultLocale: 'en',
},
});Dynamic routes via server API
// server/api/__sitemap__/urls.ts
import { serverQueryContent } from '#content/server';
export default defineSitemapEventHandler(async (event) => {
const posts = await serverQueryContent(event, 'blog')
.where({ _draft: false, published: { $lte: new Date() } })
.find();
return posts.map((p) => ({
loc: `/blog/${p._path.replace('/blog/', '')}`,
lastmod: p.updatedAt,
changefreq: 'weekly',
priority: 0.7,
}));
});Common Nuxt Sitemap Issues
- Dynamic routes (
/blog/[slug]) missing because nosourcesconfig @nuxtjs/i18ncreating locale paths without hreflang whenautoI18nis off- SSR routes missing - the module only sees prerendered or explicitly-listed URLs
- Private routes leaking because
defineRouteRuleswasn't applied site.urlresolving to a preview or Vercel branch domain- Nuxt Content posts excluded when the server handler filters wrong
- Sitemap XML cached too long by Nitro, serving stale URLs for days
- Module version mismatch - v5 has different config keys than v4
i18n handling
With autoI18n: true, the sitemap module picks up your i18n locales automatically and emits xhtml:link alternates inside each URL entry. Verify the output by requesting /sitemap.xml and looking for the xhtml:link tags. If they're missing, check that @nuxtjs/i18n is listed before @nuxtjs/sitemap in the modules array - order matters for module detection.
Step-by-Step Fix Guide
- Install with
npx nuxi module add sitemap - Set
site.urlto production at the top level ofnuxt.config.ts - Create
server/api/__sitemap__/urls.tsfor dynamic routes - Set
sitemap.sourcesto include your API handler - Add
excludepatterns for admin, dashboard, preview - Enable
autoI18n: trueif using@nuxtjs/i18n - Use
routeRulesordefineRouteRulesfor noindex on private pages - Verify with
curl https://yoursite.com/sitemap.xml - Submit to Google Search Console