{
  "$type": "site.standard.document",
  "description": "Here's how you can create a Nuxt module to generate a sitemap automatically.",
  "path": "/blog/creating-your-own-sitemap-module",
  "publishedAt": "2022-11-12T15:08:11.947Z",
  "site": "at://did:plc:jbeaa5kdaladzwq3r7f5xgwe/site.standard.publication/3gtfwwbnks225",
  "tags": [
    "nuxt",
    "module",
    "sitemap"
  ],
  "textContent": "When you're dotting the i's and crossing the t's of a shiny new Nuxt website, you will almost certainly want to ensure your site has a sitemap so that search engines know what pages of your site to index. At the moment, the Nuxt sitemap module [https://github.com/nuxt-community/sitemap-module] hasn't yet been updated for Nuxt 3 [https://v3.nuxtjs.org/]. But that shouldn't hold you back; let's make a quick-n-dirty module to generate a sitemap.\n\n\nDECIDING ON THE REQUIREMENTS\n\nHere's what we need to achieve for the sitemap I have in mind:\n\n 1. It's for a static site - so no need to fetch pages at runtime. (If this is something you need, look below instead.)\n\n 2. We want both raw XML and gzipped sitemaps.\n\n\nSCAFFOLDING A MODULE\n\nI routinely extract boilerplate out into VS Code [https://code.visualstudio.com/] snippets. Here's my snippet for a module. If you want to add it into your own settings, type Cmd-Shift-P, select Snippets: Configure User Snippets and then typescript.json (TypeScript).\n\n{\n  \"Nuxt Module\": {\n    \"prefix\": \"mod\",\n    \"body\": [\n      \"import { defineNuxtModule, useNuxt } from '@nuxt/kit'\",\n      \"\",\n      \"export default defineNuxtModule({\",\n      \"  meta: {\",\n      \"    name: '$1',\",\n      \"  },\",\n      \"  setup () {\",\n      \"    const nuxt = useNuxt()\",\n      \"    $2\",\n      \"  },\",\n      \"})\"\n    ]\n  }\n}\n\n\nTo be fair, this isn't much boilerplate, but still - it saves time.\n\nStart by creating a new file in ~/modules/sitemap.ts and type mod + Tab to fill in the scaffolding. Hey presto - we have a Nuxt module!\n\nThe good news is that Nitro stores a list of all the routes that have been prerendered; all we need to do is get this list. We can do this by hooking into nitro:init to get access to the Nitro builder. It has its own hooks, and we can use the Nitro close hook to output our sitemap at the very end of the build process.\n\nconst nuxt = useNuxt()\nnuxt.hook('nitro:init', nitro => {\n  nitro.hooks.hook('close', async () => {\n    const routes = nitro._prerenderedRoutes\n      // you might also have other logic to ensure only pages are included\n      ?.filter(r => r.fileName?.endsWith('.html'))\n      .map(r => r.route)\n\n    if (!routes?.length) return\n    // ...\n  })\n})\n\n\nNow we just need to convert these routes into a sitemap and write it to disk. The good news is that it's not that complex of a file format, and we're not planning on taking advantage of any advanced features of the sitemap like <priority> or <lastmod> for our simple little static site. So something like this should work just fine:\n\nconst timestamp = new Date().toISOString()\nconst sitemap = [\n  '<?xml version=\"1.0\" encoding=\"UTF-8\"?>',\n  '<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">',\n  ...routes.map(route =>\n    [\n      '<url>',\n      `  <loc>https://yourdomain.com${route}</loc>`,\n      `  <lastmod>${timestamp}</lastmod>`,\n      '</url>',\n    ].join('')\n  ),\n  '</urlset>',\n].join('')\n\n\nFinally, all we need to do is write that to disk.\n\nconst dir = nitro.options.output.publicDir\nawait writeFile(join(dir, 'sitemap.xml'), sitemap)\nawait writeFile(join(dir, 'sitemap.xml.gz'), gzipSync(sitemap))\n\n\nHere's the full module:\n\nimport { writeFile } from 'node:fs/promises'\nimport { gzipSync } from 'node:zlib'\nimport { defineNuxtModule, useNuxt } from '@nuxt/kit'\nimport { join } from 'pathe'\n\nexport default defineNuxtModule({\n  meta: {\n    name: 'sitemap',\n  },\n  setup() {\n    const nuxt = useNuxt()\n    nuxt.hook('nitro:init', nitro => {\n      nitro.hooks.hook('close', async () => {\n        const routes = nitro._prerenderedRoutes\n          ?.filter(r => r.fileName?.endsWith('.html'))\n          .map(r => r.route)\n        if (!routes?.length) return\n        const timestamp = new Date().toISOString()\n        const sitemap = [\n          `<?xml version=\"1.0\" encoding=\"UTF-8\"?>`,\n          `<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">`,\n          ...routes.map(\n            route =>\n              `<url><loc>https://yourdomain.com${route}</loc><lastmod>${timestamp}</lastmod></url>`\n          ),\n          `</urlset>`,\n        ].join('')\n        const dir = nitro.options.output.publicDir\n        await writeFile(join(dir, 'sitemap.xml'), sitemap)\n        await writeFile(join(dir, 'sitemap.xml.gz'), gzipSync(sitemap))\n      })\n    })\n  },\n})\n\n\n\nENABLING THE MODULE\n\nAll you need to do to enable your new module is to add it to your nuxt.config file.\n\nexport default defineNuxtConfig({\n  modules: ['~/modules/sitemap'],\n})\n\n\nNow you can run nuxi generate and check your .output/public folder to make sure that sitemap.xml and sitemap.xml.gz are present and correct!\n\n\nA DIFFERENT APPROACH FOR A DYNAMIC SITEMAP\n\nAlternatively, your website may be dynamic (for example, the page slugs may come from a CMS) or you may not be prerendering your routes. In this case, you can skip the module entirely.\n\nInstead, create ~/server/routes/sitemap.xml.get.ts and add the following:\n\nfunction ()\nexport default defineEventHandler(async event => {\n  // perform async logic\n  const routes = await fetchMyRoutesFromCMS()\n\n  // copy the logic from the module above though you might consider,\n  // if relevant, using your CMS's modified date for <lastmod> instead\n  const timestamp = new Date().toISOString()\n  const sitemap = [\n    '<?xml version=\"1.0\" encoding=\"UTF-8\"?>',\n    '<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">',\n    ...routes.map(\n      route => [\n          '<url>',\n          `  <loc>https://yourdomain.com${route}</loc>`,\n          `  <lastmod>${timestamp}</lastmod>`,\n          '</url>'\n        ].join('')\n    ),\n    '</urlset>',\n  ].join('')\n\n  setHeader(event, 'content-type', 'application/xml')\n  return sitemap\n})\n\n\nYou can then prerender this, if it isn't going to change, with a line in your config file:\n\nexport default defineNuxtConfig({\n  nitro: {\n    prerender: {\n      routes: ['/sitemap.xml'],\n    },\n  },\n})\n\n\nIf you need it to be dynamic but would benefit from light caching, you can use defineCachedEventHandler instead of defineEventHandler and Nitro will apply some optimisations for you.",
  "title": "Creating your own sitemap module for Nuxt",
  "updatedAt": "2026-05-18T10:18:03.927Z"
}