---
title: "How to Generate a Sitemap in Vue 3 · Nuxt SEO"
canonical_url: "https://nuxtseo.com/learn-seo/vue/controlling-crawlers/sitemaps"
last_updated: "2026-07-16T12:00:00.000Z"
meta:
  author: "Harlan Wilton"
  description: "Generate an XML sitemap for a Vue app with a Vite plugin, server-side route, or build script, then get it into Search Console."
  "og:description": "Generate an XML sitemap for a Vue app with a Vite plugin, server-side route, or build script, then get it into Search Console."
  "og:title": "How to Generate a Sitemap in Vue 3 · Nuxt SEO"
---

Nuxt SEO on GitHub

# **How to Generate a Sitemap in Vue 3**

Generate an XML sitemap for a Vue app with a Vite plugin, server-side route, or build script, then get it into Search Console.

[Harlan Wilton](https://x.com/harlan-zw)8 mins read Published **Nov 3, 2024** Updated **Jul 16, 2026**

**What you'll learn**

- Sitemaps are limited to 50,000 URLs or 50MB; use a sitemap index for larger sites
- Only include indexable pages, skipping noindexed, redirected, or error pages
- Submit to Google Search Console and reference the sitemap in robots.txt

The `**sitemap.xml**` file helps search engines discover your pages. Google considers sites "small" if they have [**~~500 pages or fewer~~**](https://developers.google.com/search/docs/crawling-indexing/sitemaps/overview). You likely need a sitemap if you exceed this, have a new site with few backlinks, or update content frequently. Pair it with a solid [**~~internal linking structure~~**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/internal-linking) and a [**~~pre-launch warmup~~**](https://nuxtseo.com/learn-seo/pre-launch-warmup) strategy to speed up discovery.

## Static Sitemap

For small sites under 100 pages, create a static sitemap in your public directory:

```dir
public/
  sitemap.xml
```

Add your URLs with proper formatting:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://mysite.com/</loc>
    <lastmod>2026-01-03</lastmod>
  </url>
  <url>
    <loc>https://mysite.com/about</loc>
    <lastmod>2026-01-15</lastmod>
  </url>
</urlset>
```

Sitemaps are limited to [**~~50,000 URLs or 50MB uncompressed~~**](https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap). Use UTF-8 encoding and absolute URLs only.

## Build-Time Generation with Vite

For most Vue projects, use [**~~vite-plugin-sitemap~~**](https://github.com/jbaubree/vite-plugin-sitemap) to auto-generate sitemaps during build:

vite.config.ts

```ts
import Sitemap from 'vite-plugin-sitemap'

export default defineConfig({
  plugins: [
    Sitemap({
      hostname: 'https://mysite.com',
      dynamicRoutes: [
        '/blog/post-1',
        '/blog/post-2'
      ]
    })
  ]
})
```

After running `**npm build**`, this generates `**sitemap.xml**` and `**robots.txt**` in your dist folder.

If you're using Vue Router, generate routes from your router configuration instead of listing them by hand:

vite.config.ts

```ts
import Sitemap from 'vite-plugin-sitemap'
import routes from './src/router/routes'

export default defineConfig({
  plugins: [
    Sitemap({
      hostname: 'https://mysite.com',
      dynamicRoutes: routes.map(route => route.path)
    })
  ]
})
```

Avoid hash-based routing (`**/#/about**`): search engines ignore everything after the `**#**`, so hash-routed pages collapse into a single URL and can't be listed individually in a sitemap. Use history mode instead.

## Server-Side Sitemap Generation

For sites with frequently changing content (e-commerce, blogs, news), generate sitemaps server-side:

```ts
import express from 'express'

const app = express()

app.get('/sitemap.xml', async (req, res) => {
  const pages = await fetchAllPages()

  const urls = pages.map(page => `
  <url>
    <loc>https://mysite.com${page.path}</loc>
    <lastmod>${page.updatedAt}</lastmod>
  </url>`).join('\n')

  const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urls}
</urlset>`

  res.type('application/xml').send(sitemap)
})
```

This works with any [**~~Node.js~~**](https://nodejs.org) server or SSR framework. For SPAs without SSR, use build-time generation instead: you need every route listed before the build runs, which is what the [**~~Vite~~**](https://vite.dev) plugin's `**dynamicRoutes**` option is for.

## XML Sitemap Tags

A basic sitemap uses these elements:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://mysite.com/page</loc>
    <lastmod>2026-01-03</lastmod>
  </url>
</urlset>
```

| **Tag** | **Status** | **Google Uses?** |
| --- | --- | --- |
| `**<loc>**` | Required | Yes, the page URL |
| `**<lastmod>**` | Recommended | Yes, [**~~if consistently accurate~~**](https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap) |
| `**<changefreq>**` | Skip | [**~~No, Google ignores this~~**](https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap) |
| `**<priority>**` | Skip | [**~~No, Google ignores this~~**](https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap) |

Google only uses `**<lastmod>**` [**~~if it's consistently and verifiably accurate~~**](https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap), meaning it can compare the date against the page's actual last modification. Update it when main content, structured data, or links change, not on every deploy.

Don't bother with `**<changefreq>**` or `**<priority>**`: they increase file size without adding value.

## Sitemap Index for Large Sites

If you exceed 50,000 URLs or 50MB, [**~~split your sitemap into multiple files~~**](https://developers.google.com/search/docs/crawling-indexing/sitemaps/large-sitemaps):

sitemap-index.xml

```xml
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <sitemap>
    <loc>https://mysite.com/sitemap-products.xml</loc>
    <lastmod>2026-01-03</lastmod>
  </sitemap>
  <sitemap>
    <loc>https://mysite.com/sitemap-blog.xml</loc>
    <lastmod>2026-01-10</lastmod>
  </sitemap>
</sitemapindex>
```

Submit the index file to Google Search Console and it will crawl every sitemap listed inside it.

## Push-Based Discovery with IndexNow

Sitemaps are pull-based: crawlers check them on their own schedule. [**~~IndexNow~~**](https://www.indexnow.org/) is push-based. It's a protocol that notifies search engines the moment you create, update, or delete a page, instead of making them wait to rediscover it on the next crawl. Without it, [**~~discovery can take days to weeks~~**](https://www.indexnow.org/); Bing, Yandex, Seznam, Naver, and Yep all support it. Google does not.

Vue has no built-in module for this. Ping the endpoint yourself after a deploy:

```ts
await fetch('https://api.indexnow.org/indexnow', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    host: 'mysite.com',
    key: 'your-indexnow-key',
    keyLocation: 'https://mysite.com/your-indexnow-key.txt',
    urlList: ['https://mysite.com/new-page']
  })
})
```

## Specialized Sitemaps

### News Sitemap

News publishers should create [**~~separate news sitemaps~~**](https://developers.google.com/search/docs/crawling-indexing/sitemaps/news-sitemap) with articles from the last 2 days:

sitemap-news.xml

```xml
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
        xmlns:news="http://www.google.com/schemas/sitemap-news/0.9">
  <url>
    <loc>https://mysite.com/article</loc>
    <news:news>
      <news:publication>
        <news:name>Site Name</news:name>
        <news:language>en</news:language>
      </news:publication>
      <news:publication_date>2026-01-29T12:00:00+00:00</news:publication_date>
      <news:title>Article Title</news:title>
    </news:news>
  </url>
</urlset>
```

Remove articles older than 2 days to keep the sitemap fresh; don't create new sitemaps daily, update the existing one.

### Image Sitemap

For galleries or image-heavy sites, add image metadata:

sitemap.xml

```xml
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
        xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">
  <url>
    <loc>https://mysite.com/page</loc>
    <image:image>
      <image:loc>https://mysite.com/image.jpg</image:loc>
      <image:title>Image Title</image:title>
    </image:image>
  </url>
</urlset>
```

[**~~Image sitemaps help Google find images loaded via JavaScript~~**](https://developers.google.com/search/docs/crawling-indexing/sitemaps/image-sitemaps) or not directly linked in HTML.

## Submit to Google Search Console

After generating your sitemap, [**~~submit it to Google Search Console~~**](https://support.google.com/webmasters/answer/7451001):

1. [**~~Verify site ownership~~**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/search-console) (DNS, HTML file upload, or Google Analytics)
2. Navigate to **Sitemaps** under **Indexing**
3. Enter your sitemap URL: `**https://mysite.com/sitemap.xml**`
4. Click **Submit**

Google processes the sitemap and reports status:

- **Success**: sitemap accepted, pages discovered
- **Pending**: processing in progress
- **Couldn't fetch**: URL wrong or blocked by robots.txt

Check the Sitemaps report for coverage stats, indexing errors, and discovered URLs.

### Before Submitting

Validate your sitemap:

- XML syntax is valid (use an [**~~online validator~~**](https://www.xml-sitemaps.com/validate-xml-sitemap.html))
- All URLs return 200 status (not 404 or 500)
- URLs match [**~~canonical URLs~~**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/canonical-urls) exactly
- `**<lastmod>**` dates are accurate
- File uses UTF-8 encoding

### Alternative Submission

Reference your sitemap in `**robots.txt**`:

public/robots.txt

```txt
User-agent: *
Allow: /

Sitemap: https://mysite.com/sitemap.xml
```

Google discovers this automatically when [**~~crawling your robots.txt~~**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/robots-txt).

### Troubleshooting "Couldn't Fetch"

Search Console sometimes shows **"Couldn't fetch"** or **"Sitemap could not be read"** even though the sitemap validates and loads fine in a browser. [**~~Google lists several causes~~**](https://support.google.com/webmasters/answer/7451001) for this status: the sitemap is blocked by `**robots.txt**`, the URL is wrong (a 404), the site has an unresolved manual action, or the fetch simply failed.

1. **Rule out the obvious first.** Confirm the sitemap URL loads directly, isn't blocked in `**robots.txt**`, and your site has no pending manual action.
2. **Use the URL Inspection tool** in Search Console. Paste your sitemap URL and click **Live test**. If Page fetch shows "Successful" and Crawl allowed shows "Yes", the sitemap itself is fine and the error is transient.
3. **Give it time.** Google says some fetch errors are transient and resolve on later crawl attempts, so wait and check back rather than resubmitting immediately.

If it's still failing after checking the above, remove and resubmit the sitemap in Search Console to force a re-fetch, or rename the sitemap path (for example `**/sitemap.xml**` to `**/sitemap-index.xml**`) so Google treats it as new.

Using Nuxt? [**~~Nuxt SEO~~**](https://nuxtseo.com/docs/nuxt-seo/getting-started/introduction) handles sitemap generation, indexing, and robots.txt automatically. [**~~Learn more about sitemaps in Nuxt →~~**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/sitemaps)

## Checklist

**Checklist**

- Sitemap includes every indexable URL, none blocked by `**robots.txt**` or `**noindex**`
- URLs use history mode, no `**#**` hash routing
- `**<lastmod>**` dates are accurate, not blanket-set on every build
- Sitemap stays under 50,000 URLs and 50MB, split into an index if not
- Sitemap submitted in Google Search Console and referenced in `**robots.txt**`

[**The 2026 SEO Checklist for Nuxt & Vue ** Pre-launch setup, post-launch verification, and ongoing monitoring. Interactive checklist with links to every guide.](https://nuxtseo.com/learn-seo/checklist) [Haven't launched yet? Start with the **Pre-Launch Warmup**](https://nuxtseo.com/learn-seo/pre-launch-warmup)

---

### **Related **

[**Robots.txt Guide**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/robots-txt)

[**Canonical URLs**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/canonical-urls)

[**Vue SEO Rendering**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/rendering)

### **Open Source **

[**jbaubree/vite-plugin-sitemap**** Public **](https://github.com/jbaubree/vite-plugin-sitemap)

[**Robots.txt** Write or generate robots.txt server-side in Vue, and avoid the mistakes that block Googlebot or leak your admin paths.](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/robots-txt) [**Robot Meta Tag** Set noindex, nofollow, and AI Overview opt-outs with useSeoMeta in Vue, and avoid the mistakes that silently deindex working pages.](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/meta-tags)

**On this page**

- [Static Sitemap](#static-sitemap)
- [Build-Time Generation with Vite](#build-time-generation-with-vite)
- [Server-Side Sitemap Generation](#server-side-sitemap-generation)
- [XML Sitemap Tags](#xml-sitemap-tags)
- [Sitemap Index for Large Sites](#sitemap-index-for-large-sites)
- [Push-Based Discovery with IndexNow](#push-based-discovery-with-indexnow)
- [Specialized Sitemaps](#specialized-sitemaps)
- [Submit to Google Search Console](#submit-to-google-search-console)
- [Checklist](#checklist)