Skip to content

Sitemap Generation

Learn how to generate and customize sitemaps for your VitePress site.

Automatic Sitemap Generation

Basic Configuration

VitePress can automatically generate sitemaps:

javascript
// .vitepress/config.js
export default {
  sitemap: {
    hostname: 'https://example.com'
  }
}

Generated Sitemap

This creates a sitemap.xml file in your build output:

xml
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://example.com/</loc>
    <lastmod>2024-01-15T10:00:00.000Z</lastmod>
    <changefreq>weekly</changefreq>
    <priority>1.0</priority>
  </url>
  <url>
    <loc>https://example.com/guide/</loc>
    <lastmod>2024-01-15T10:00:00.000Z</lastmod>
    <changefreq>weekly</changefreq>
    <priority>0.8</priority>
  </url>
</urlset>

Advanced Configuration

Custom Transform Function

Customize sitemap entries:

javascript
// .vitepress/config.js
export default {
  sitemap: {
    hostname: 'https://example.com',
    transformItems: (items) => {
      return items.map(item => {
        // Customize each URL entry
        if (item.url === '/') {
          return {
            ...item,
            changefreq: 'daily',
            priority: 1.0
          }
        }
        
        if (item.url.startsWith('/guide/')) {
          return {
            ...item,
            changefreq: 'weekly',
            priority: 0.8
          }
        }
        
        if (item.url.startsWith('/api/')) {
          return {
            ...item,
            changefreq: 'monthly',
            priority: 0.6
          }
        }
        
        return {
          ...item,
          changefreq: 'monthly',
          priority: 0.5
        }
      })
    }
  }
}

Filtering URLs

Exclude specific pages from sitemap:

javascript
// .vitepress/config.js
export default {
  sitemap: {
    hostname: 'https://example.com',
    transformItems: (items) => {
      // Filter out specific pages
      return items.filter(item => {
        // Exclude draft pages
        if (item.url.includes('/draft/')) {
          return false
        }
        
        // Exclude private pages
        if (item.url.includes('/private/')) {
          return false
        }
        
        // Exclude 404 and other special pages
        if (item.url.includes('/404')) {
          return false
        }
        
        return true
      })
    }
  }
}

Multi-language Sitemaps

Hreflang Support

Add language alternatives:

javascript
// .vitepress/config.js
export default {
  sitemap: {
    hostname: 'https://example.com',
    transformItems: (items) => {
      return items.map(item => {
        const url = item.url
        
        // Add language alternatives
        const links = []
        
        // English version (default)
        links.push({
          lang: 'en',
          url: `https://example.com${url}`
        })
        
        // Chinese version
        if (!url.startsWith('/zh/')) {
          links.push({
            lang: 'zh-CN',
            url: `https://example.com/zh${url}`
          })
        }
        
        // Spanish version
        if (!url.startsWith('/es/')) {
          links.push({
            lang: 'es',
            url: `https://example.com/es${url}`
          })
        }
        
        return {
          ...item,
          links
        }
      })
    }
  }
}

Separate Language Sitemaps

Generate separate sitemaps for each language:

javascript
// .vitepress/config.js
export default {
  sitemap: {
    hostname: 'https://example.com',
    transformItems: (items) => {
      const sitemaps = {
        main: [],
        zh: [],
        es: []
      }
      
      items.forEach(item => {
        if (item.url.startsWith('/zh/')) {
          sitemaps.zh.push({
            ...item,
            url: item.url.replace('/zh', '')
          })
        } else if (item.url.startsWith('/es/')) {
          sitemaps.es.push({
            ...item,
            url: item.url.replace('/es', '')
          })
        } else {
          sitemaps.main.push(item)
        }
      })
      
      // Generate separate sitemap files
      // This would require custom build logic
      return sitemaps.main
    }
  }
}

Dynamic Content

API-driven Content

Include dynamically generated pages:

javascript
// .vitepress/config.js
export default {
  async buildEnd(siteConfig) {
    // Fetch dynamic content
    const posts = await fetch('https://api.example.com/posts')
      .then(res => res.json())
    
    // Generate additional sitemap entries
    const dynamicUrls = posts.map(post => ({
      url: `/blog/${post.slug}`,
      lastmod: new Date(post.updatedAt),
      changefreq: 'weekly',
      priority: 0.7
    }))
    
    // Add to sitemap (requires custom implementation)
    await generateCustomSitemap(dynamicUrls)
  },
  
  sitemap: {
    hostname: 'https://example.com'
  }
}

Database Content

Include content from databases:

javascript
// utils/sitemap-generator.js
import { createConnection } from 'mysql2/promise'

export async function generateDynamicSitemap() {
  const connection = await createConnection({
    host: process.env.DB_HOST,
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_NAME
  })
  
  const [articles] = await connection.execute(`
    SELECT slug, updated_at, priority 
    FROM articles 
    WHERE published = 1
  `)
  
  const urls = articles.map(article => ({
    url: `/articles/${article.slug}`,
    lastmod: new Date(article.updated_at),
    changefreq: 'weekly',
    priority: article.priority || 0.6
  }))
  
  await connection.end()
  return urls
}

Custom Sitemap Generation

Manual Sitemap Creation

Create custom sitemap generation:

javascript
// scripts/generate-sitemap.js
import { writeFileSync } from 'fs'
import { resolve } from 'path'

function generateSitemap(urls, hostname) {
  const sitemap = `<?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">
${urls.map(url => `  <url>
    <loc>${hostname}${url.url}</loc>
    <lastmod>${url.lastmod.toISOString()}</lastmod>
    <changefreq>${url.changefreq}</changefreq>
    <priority>${url.priority}</priority>
${url.links ? url.links.map(link => 
    `    <xhtml:link rel="alternate" hreflang="${link.lang}" href="${link.url}"/>`
  ).join('\n') : ''}
  </url>`).join('\n')}
</urlset>`

  return sitemap
}

// Usage
const urls = [
  {
    url: '/',
    lastmod: new Date(),
    changefreq: 'daily',
    priority: 1.0
  },
  {
    url: '/guide/',
    lastmod: new Date(),
    changefreq: 'weekly',
    priority: 0.8,
    links: [
      { lang: 'en', url: 'https://example.com/guide/' },
      { lang: 'zh-CN', url: 'https://example.com/zh/guide/' }
    ]
  }
]

const sitemap = generateSitemap(urls, 'https://example.com')
writeFileSync(resolve('dist/sitemap.xml'), sitemap)

Sitemap Index

Create sitemap index for large sites:

javascript
// scripts/generate-sitemap-index.js
function generateSitemapIndex(sitemaps, hostname) {
  const sitemapIndex = `<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${sitemaps.map(sitemap => `  <sitemap>
    <loc>${hostname}/${sitemap.filename}</loc>
    <lastmod>${sitemap.lastmod.toISOString()}</lastmod>
  </sitemap>`).join('\n')}
</sitemapindex>`

  return sitemapIndex
}

// Usage
const sitemaps = [
  {
    filename: 'sitemap-pages.xml',
    lastmod: new Date()
  },
  {
    filename: 'sitemap-posts.xml',
    lastmod: new Date()
  },
  {
    filename: 'sitemap-products.xml',
    lastmod: new Date()
  }
]

const sitemapIndex = generateSitemapIndex(sitemaps, 'https://example.com')
writeFileSync(resolve('dist/sitemap.xml'), sitemapIndex)

SEO Optimization

Priority Guidelines

Set appropriate priorities:

javascript
const priorityMap = {
  '/': 1.0,                    // Homepage
  '/guide/': 0.9,              // Main sections
  '/api/': 0.9,
  '/guide/getting-started': 0.8, // Important pages
  '/guide/configuration': 0.8,
  '/blog/': 0.7,               // Blog section
  '/blog/[slug]': 0.6,         // Individual posts
  '/examples/': 0.5,           // Examples
  '/faq/': 0.4                 // FAQ pages
}

Change Frequency Guidelines

Set realistic change frequencies:

javascript
const changeFreqMap = {
  '/': 'daily',                // Homepage updates frequently
  '/blog/': 'daily',           // Blog index changes often
  '/guide/': 'weekly',         // Documentation updates weekly
  '/api/': 'weekly',           // API docs update weekly
  '/blog/[slug]': 'monthly',   // Individual posts rarely change
  '/about': 'yearly'           // Static pages change rarely
}

Validation and Testing

Sitemap Validation

Validate your sitemap:

javascript
// utils/validate-sitemap.js
import { readFileSync } from 'fs'
import { DOMParser } from 'xmldom'

export function validateSitemap(sitemapPath) {
  const sitemapContent = readFileSync(sitemapPath, 'utf-8')
  const parser = new DOMParser()
  const doc = parser.parseFromString(sitemapContent, 'text/xml')
  
  const errors = []
  const urls = doc.getElementsByTagName('url')
  
  for (let i = 0; i < urls.length; i++) {
    const url = urls[i]
    const loc = url.getElementsByTagName('loc')[0]?.textContent
    const priority = url.getElementsByTagName('priority')[0]?.textContent
    
    // Validate URL format
    if (!loc || !isValidUrl(loc)) {
      errors.push(`Invalid URL at index ${i}: ${loc}`)
    }
    
    // Validate priority range
    if (priority && (parseFloat(priority) < 0 || parseFloat(priority) > 1)) {
      errors.push(`Invalid priority at ${loc}: ${priority}`)
    }
  }
  
  return errors
}

function isValidUrl(string) {
  try {
    new URL(string)
    return true
  } catch (_) {
    return false
  }
}

Testing Sitemap Accessibility

Test sitemap accessibility:

javascript
// scripts/test-sitemap.js
import fetch from 'node-fetch'

async function testSitemapUrls(sitemapUrl) {
  const response = await fetch(sitemapUrl)
  const sitemapContent = await response.text()
  
  // Parse URLs from sitemap
  const urls = extractUrlsFromSitemap(sitemapContent)
  
  const results = []
  
  for (const url of urls) {
    try {
      const response = await fetch(url, { method: 'HEAD' })
      results.push({
        url,
        status: response.status,
        accessible: response.ok
      })
    } catch (error) {
      results.push({
        url,
        status: 'ERROR',
        accessible: false,
        error: error.message
      })
    }
  }
  
  return results
}

Best Practices

Content Organization

  • Include all important pages
  • Exclude duplicate content
  • Set realistic change frequencies
  • Use appropriate priorities

Performance

  • Keep sitemaps under 50MB
  • Limit to 50,000 URLs per sitemap
  • Use sitemap index for large sites
  • Compress sitemaps when possible

SEO

  • Submit sitemaps to search engines
  • Update sitemaps regularly
  • Include lastmod dates
  • Use canonical URLs

Maintenance

  • Validate sitemaps regularly
  • Monitor sitemap errors in search console
  • Update priorities based on analytics
  • Remove outdated URLs promptly

VitePress Development Guide