Index of every page on this site: llms.txt

VitePress SEO: Sitemaps, Meta Tags, and Search

Set up VitePress's built-in sitemap, per-page meta tags, and local search, then know when you actually need Nuxt Content instead.
Harlan Wilton7 mins read Published Updated
What you'll learn
  • 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:

.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:

.vitepress/config.ts
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:

.vitepress/config.ts
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:

.vitepress/config.ts
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:

.vitepress/config.ts
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.

VitePress supports fuzzy full-text search using an in-browser index, powered by minisearch. Enable it in .vitepress/config.ts:

.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:

.vitepress/config.ts
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:

.vitepress/config.ts
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

FeatureVitePressNuxt Content
Page renderingVue SSR at build timeNuxt SSR or prerendering, according to app configuration
SearchOptional bundled local searchContent search APIs with an app-owned interface
Page routesMarkdown-based routesContent queried from Vue page routes
Request-time server logicRequires a separate serverAvailable 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.

Was this page helpful?