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