VitePress SEO: Sitemaps, Meta Tags, and Search
- VitePress pre-renders page content to HTML by default. Client-only components still depend on browser JavaScript
- Sitemap generation, per-page meta tags, and local search all ship built in, no extra packages needed for the basics
- It generates static pages. Use a server framework for request-time rendering. Protect private static files through host access controls
VitePress is a static site generator built on Vite and Vue 3, designed for documentation sites and content-heavy blogs. Page content renders to HTML at build time by default. Client-only components appear after browser JavaScript runs. Navigation after that first load behaves like an SPA.
When to Use VitePress
VitePress fits these requirements:
- You're building technical documentation with minimal setup
- Your publishing workflow builds Markdown pages ahead of requests
- Page content can be public static files, with no request-time server logic
- You want bundled code highlighting and an optional local search index
Choose Nuxt Content when you need request-time rendering, Vue page routes alongside content, or integration with Nuxt modules. VitePress itself uses Vue SSR at build time. It does not provide a request-time application server.
This guide uses VitePress 1.6.4. Start with an installed VitePress project, then add the settings below to its existing config. Build with pnpm exec vitepress build and inspect .vitepress/dist before deployment.
SEO Features
Built-in Sitemap Generation
VitePress includes native sitemap support, powered by the sitemap npm package. Enable it in .vitepress/config.ts:
export default {
sitemap: {
hostname: 'https://mysite.com'
}
}This generates sitemap.xml in .vitepress/dist during build. For <lastmod> tags, enable lastUpdated. VitePress uses each page's Git timestamp or an explicit lastUpdated frontmatter date. A build needs that history or date to emit the value:
export default {
sitemap: {
hostname: 'https://mysite.com'
},
lastUpdated: true
}Any SitemapStream option can be passed straight into the sitemap config.
Custom Sitemap Items
Use transformItems to modify sitemap entries before they're written:
export default {
sitemap: {
hostname: 'https://mysite.com',
transformItems: (items) => {
// Filter out draft pages
return items.filter(item => !item.url.includes('/drafts/'))
}
}
}This filter removes sitemap entries only. It does not unpublish, noindex, or protect built draft files. Exclude private drafts from the build or restrict access at the host.
Build Hooks for Custom Sitemaps
For custom output, the buildEnd hook runs after page generation. It does not supply an application-specific generatePageList helper. Use native sitemap generation unless your project needs a separate generator with its own installed dependencies and page inventory.
Meta Tags Configuration
Site-Level Meta Tags
Add meta tags globally using the head config:
export default {
description: 'My documentation site',
head: [
['meta', { property: 'og:type', content: 'website' }],
['meta', { property: 'og:image', content: 'https://mysite.com/og.png' }],
['link', { rel: 'icon', href: '/favicon.ico' }]
]
}User-added tags render before the closing </head> tag, after VitePress's own built-in tags.
Page-Level Meta Tags
Override meta tags per page using frontmatter:
---
description: Custom description for this page
head:
-
- meta
- property: og:title
content: Custom OG Title
-
- meta
- name: keywords
content: vitepress, seo, vue
---VitePress 1.6.4 merges site and page head entries and replaces matching meta tags. Use the description field for descriptions. Assign each dynamic property in one place so its value is clear, then inspect the generated HTML.
Dynamic Meta Tags
For meta tags computed from page data, use transformPageData:
export default {
transformPageData(pageData) {
pageData.frontmatter.head ??= []
pageData.frontmatter.head.push([
'meta',
{
property: 'og:title',
content: pageData.frontmatter.layout === 'home'
? 'Homepage - My Site'
: pageData.title
}
])
}
}transformPageData runs during both dev and build. For a build-only equivalent, use transformHead: it runs when generating production HTML. Check those tags in a build, rather than relying on the dev response.
Built-in Search
VitePress supports fuzzy full-text search using an in-browser index, powered by minisearch. Enable it in .vitepress/config.ts:
export default {
themeConfig: {
search: {
provider: 'local'
}
}
}No external service required; search runs in the browser against an indexed bundle built at compile time. If the local index becomes too large for your readers, evaluate Algolia DocSearch. It requires a configured index and search credentials:
export default {
themeConfig: {
search: {
provider: 'algolia',
options: {
appId: 'YOUR_APP_ID',
apiKey: 'YOUR_API_KEY',
indexName: 'YOUR_INDEX_NAME'
}
}
}
}Clean URLs
VitePress can serve URLs without the .html extension:
export default {
cleanUrls: true
}Enable this only when your host serves /page.html for requests to /page without redirecting back to the extension. The setting changes generated links and sitemap URLs. It does not configure host redirects. If both variants are accessible, choose a preferred form and align internal links, sitemap entries, and canonical tags with it.
VitePress vs Nuxt Content
| Feature | VitePress | Nuxt Content |
|---|---|---|
| Page rendering | Vue SSR at build time | Nuxt SSR or prerendering, according to app configuration |
| Search | Optional bundled local search | Content search APIs with an app-owned interface |
| Page routes | Markdown-based routes | Content queried from Vue page routes |
| Request-time server logic | Requires a separate server | Available in a server-deployed Nuxt app |
Choose by rendering and publishing requirements. Package downloads and unmatched build timings do not establish which option serves your readers better.
Limitations
- No request-time page renderer. Page HTML is generated at build time. Client-side login cannot protect private content already published in those files.
- Build-time content. Rebuild when published page content changes. Browser components can still request data after hydration.
- Hosting remains separate. Configure redirects, access control, and clean URL support on the host.
- Separate routing systems. If you combine VitePress with an existing app, define which deployment owns each URL. A separate site can avoid overlapping routes.
Using Nuxt? @nuxtjs/seo integrates sitemap, robots.txt, structured data, and OG image modules. Configure your site URL and the features your pages need. Learn more about Nuxt Content for documentation.
Pre-launch setup, post-launch verification, and ongoing monitoring. Interactive checklist with links to every guide.