Learn-skills.dev web-meta-framework-vitepress
VitePress 1.x — Vue-powered static site generator for documentation sites, built on Vite
git clone https://github.com/NeverSight/learn-skills.dev
T=$(mktemp -d) && git clone --depth=1 https://github.com/NeverSight/learn-skills.dev "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/skills-md/agents-inc/skills/web-meta-framework-vitepress" ~/.claude/skills/neversight-learn-skills-dev-web-meta-framework-vitepress && rm -rf "$T"
data/skills-md/agents-inc/skills/web-meta-framework-vitepress/SKILL.mdVitePress
Quick Guide: VitePress is a Vue-powered static site generator built on Vite, designed for documentation. All config lives in
. Use.vitepress/config.tsfor type safety. Sidebar accepts arrays (single) or objects keyed by path prefix (multi-sidebar). Data loaders (defineConfig()) run at build time and ship only serialized results to the client. Vue components work directly in Markdown via*.data.ts. Extend the default theme through layout slots and CSS variables rather than forking it.<script setup>Current stable version: VitePress 1.6.x (2026). Uses Vite 6+ and Vue 3.5+.
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md
(You MUST use
with .vitepress/config.ts
for all site configuration — VitePress does not support config outside defineConfig()
).vitepress/
(You MUST use data loader files (
) for build-time data — never fetch data at runtime in SSR-unsafe ways)*.data.ts
(You MUST handle SSR compatibility — no bare
, window
, or browser APIs outside document
or onMounted
)<ClientOnly>
(You MUST extend the default theme via
and layout slots — do not fork the entire theme)extends: DefaultTheme
(You MUST use
for markdown collection pages — it handles caching, watching, and frontmatter extraction)createContentLoader()
</critical_requirements>
Auto-detection: VitePress, vitepress, .vitepress/config, defineConfig vitepress, createContentLoader, vitepress/theme, DefaultTheme, useData, useSidebar, markdown-it plugin vitepress, vitepress deploy
When to use:
- Building documentation sites from Markdown files
- Creating blog index/archive pages with
createContentLoader - Customizing the default theme (nav, sidebar, layout slots, CSS variables)
- Adding Vue components to Markdown pages
- Configuring markdown-it plugins for extended syntax
- Setting up multi-sidebar navigation by path prefix
- Generating sitemaps and other build artifacts with build hooks
- Internationalization (i18n) with multi-locale routing
When NOT to use:
- Full web applications with complex client-side routing (use a web framework)
- Sites requiring server-side runtime logic (VitePress is static output)
- Projects already using Docusaurus, Nextra, or Starlight (those are separate ecosystems)
- Content that needs a CMS backend (VitePress reads Markdown files at build time)
- API documentation from OpenAPI specs (use a dedicated OpenAPI tool)
<patterns>
Core Patterns
Pattern 1: Site Configuration
All configuration lives in
.vitepress/config.ts. Use defineConfig() for type checking and autocompletion.
import { defineConfig } from "vitepress"; export default defineConfig({ title: "My Docs", description: "Documentation site", cleanUrls: true, lastUpdated: true, sitemap: { hostname: "https://docs.example.com" }, themeConfig: { nav: [ { text: "Guide", link: "/guide/" }, { text: "API", link: "/api/" }, ], sidebar: { /* see Pattern 2 */ }, socialLinks: [{ icon: "github", link: "https://github.com/org/repo" }], search: { provider: "local" }, editLink: { pattern: "https://github.com/org/repo/edit/main/docs/:path", }, }, });
Why good:
cleanUrls: true removes .html extensions, sitemap auto-generates sitemap.xml, lastUpdated reads git timestamps, search.provider: 'local' enables built-in search with zero config
Full examples: See examples/core.md for complete config, multi-sidebar, i18n, and markdown config.
Pattern 2: Multi-Sidebar
Sidebar can be an array (global) or an object keyed by URL path prefix (multi-sidebar). Each section supports
collapsed for expandable groups.
sidebar: { '/guide/': [ { text: 'Getting Started', collapsed: false, items: [ { text: 'Introduction', link: '/guide/introduction' }, { text: 'Installation', link: '/guide/installation' }, ], }, { text: 'Advanced', collapsed: true, items: [ { text: 'Data Loaders', link: '/guide/data-loading' }, { text: 'Deployment', link: '/guide/deploy' }, ], }, ], '/api/': [ { text: 'API Reference', items: [ { text: 'Config', link: '/api/config' }, { text: 'Runtime API', link: '/api/runtime' }, ], }, ], }
Why good: Each path prefix gets its own sidebar navigation,
collapsed: true keeps dense sidebars scannable
Common mistake: Using
/guide without trailing slash — VitePress matches path prefixes, so /guide/ is more precise than /guide (which would also match /guidelines)
Pattern 3: Data Loaders
Data loaders (
*.data.ts files) execute at build time. Results are serialized and shipped to client components. Use createContentLoader for Markdown collections, custom load() for arbitrary data.
// posts.data.ts import { createContentLoader } from "vitepress"; export default createContentLoader("blog/posts/*.md", { excerpt: true, transform(rawData) { return rawData .sort( (a, b) => +new Date(b.frontmatter.date) - +new Date(a.frontmatter.date), ) .map(({ url, frontmatter, excerpt }) => ({ title: frontmatter.title, url, date: frontmatter.date, excerpt, })); }, });
<!-- blog/index.md — consume in Vue --> <script setup> import { data as posts } from "./posts.data"; </script> <template> <article v-for="post in posts" :key="post.url"> <h2> <a :href="post.url">{{ post.title }}</a> </h2> <time>{{ post.date }}</time> <div v-html="post.excerpt" /> </article> </template>
Why good:
createContentLoader handles file watching in dev, caching by mtime, frontmatter extraction, and optional HTML rendering. The transform strips unnecessary data so only what's needed reaches the client bundle.
Key options:
includeSrc (raw markdown), render (full HTML), excerpt (content above first ---). Only enable what you need — render: true on hundreds of pages inflates the client bundle.
Full examples: See examples/core.md for custom loaders and
usage.buildEnd
Pattern 4: Vue Components in Markdown
Vue components work directly in
.md files. Use <script setup> for page-scoped imports. Register global components in the theme for cross-page reuse.
<script setup> import StatusBadge from '../components/StatusBadge.vue' </script> # API Reference <StatusBadge status="stable" /> This API is production-ready.
// .vitepress/theme/index.ts — global registration import DefaultTheme from "vitepress/theme"; import StatusBadge from "../components/StatusBadge.vue"; export default { extends: DefaultTheme, enhanceApp({ app }) { app.component("StatusBadge", StatusBadge); }, };
Why good: Page-scoped imports enable code-splitting (only loaded on pages that use them). Global registration is for components used across many pages.
SSR rule: Components that access browser APIs must be wrapped in
<ClientOnly> or guarded with onMounted. VitePress pre-renders all pages at build time.
Pattern 5: Theme Extension
Extend the default theme through layout slots and CSS variables. Do not fork the entire layout.
<!-- .vitepress/theme/MyLayout.vue --> <script setup> import DefaultTheme from "vitepress/theme"; import { useData } from "vitepress"; const { Layout } = DefaultTheme; const { frontmatter } = useData(); </script> <template> <Layout> <template #doc-before> <div v-if="frontmatter.author" class="author-banner"> By {{ frontmatter.author }} </div> </template> <template #doc-footer-before> <div class="feedback-widget">Was this page helpful?</div> </template> </Layout> </template>
Available layout slots:
nav-bar-title-before, nav-bar-title-after, nav-bar-content-before, nav-bar-content-after, nav-screen-content-before, nav-screen-content-after, sidebar-nav-before, sidebar-nav-after, aside-top, aside-bottom, aside-outline-before, aside-outline-after, doc-before, doc-after, doc-footer-before, doc-top, doc-bottom, home-hero-before, home-hero-after, home-features-before, home-features-after, not-found
Full examples: See examples/core.md for CSS variable overrides and custom theme setup.
Pattern 6: Build Hooks
Build hooks in config transform page data, inject head tags, or generate files at build time.
export default defineConfig({ async transformPageData(pageData) { // Add computed data available via useData() pageData.frontmatter.head ??= []; pageData.frontmatter.head.push([ "meta", { property: "og:title", content: pageData.title }, ]); }, async buildEnd(siteConfig) { // Generate files after build — RSS feeds, redirects, etc. const posts = await createContentLoader("blog/*.md").load(); // write to siteConfig.outDir }, });
Available hooks:
transformPageData (per-page, access frontmatter + route), transformHead (per-page, return head tags array), transformHtml (per-page, modify rendered HTML string), buildEnd (once after build, generate extra files)
Pattern 7: Markdown Extensions
VitePress extends standard Markdown with containers, code groups, line highlighting, and code snippets.
::: tip RECOMMENDATION Use `createContentLoader` for blog index pages. ::: ::: code-group ```ts [config.ts] export default defineConfig({ title: "Docs" }); ```
export default { title: "Docs" };
:::
<!-- Line highlighting --><!-- Import code from file -->export default { title: 'Highlighted', // highlighted description: 'Also', // highlighted }
<<< @/snippets/example.ts
<!-- Include partial markdown --> <!--@include: ./shared/header.md-->**Container types:** `info`, `tip`, `warning`, `danger`, `details` (expandable). Customize labels in `markdown.container` config. **Code annotations:** `// [!code focus]`, `// [!code ++]`, `// [!code --]`, `// [!code warning]`, `// [!code error]` > **Full reference:** See [examples/core.md](examples/core.md#markdown-extensions) for all code block features and custom container labels. --- ### Pattern 8: markdown-it Plugin Integration VitePress uses markdown-it internally. Add plugins via `markdown.config`. ```ts import { defineConfig } from 'vitepress' export default defineConfig({ markdown: { // Built-in options lineNumbers: true, toc: { level: [1, 2, 3] }, // Add custom plugins config: (md) => { md.use(require('markdown-it-footnote')) }, }, })
Why this matters: Plugins added via
markdown.config get the fully-configured markdown-it instance with VitePress's own plugins already registered. Do not create a separate markdown-it instance.
</patterns>
Examples
- Core Patterns -- Config, sidebar, data loaders, theme extension, markdown features, frontmatter, deployment
Other resources:
- Quick Reference -- Config options, frontmatter fields, CLI commands, CSS variables, layout slots
<red_flags>
RED FLAGS
High Priority Issues:
- Accessing
,window
, or browser-only APIs outsidedocument
oronMounted
(SSR will crash at build time)<ClientOnly> - Using
inrender: true
for large collections (inflates client bundle with full HTML of every page)createContentLoader - Forking the default theme layout instead of using layout slots (breaks on VitePress upgrades)
- Placing config outside
(VitePress will not find it).vitepress/config.ts - Using runtime data fetching (
in components) for data that should be loaded at build time via data loadersfetch
Medium Priority Issues:
- Sidebar path prefix without trailing slash (
matches'/guide'
too)/guidelines - Not enabling
(results incleanUrls: true
extensions in all URLs).html - Forgetting
(sitemap generates with empty URLs)sitemap.hostname - Not setting
in site config AND having git history (feature is opt-in)lastUpdated: true - Importing heavy libraries in global components when they are only used on one page (defeats code-splitting)
Common Mistakes:
- Using
as a flat array when different sections need different sidebars (use object keyed by path prefix)themeConfig.sidebar - Not running
with the correctvitepress build
for subdirectory deployments--base - Expecting dynamic routes to work like a web framework — VitePress generates static pages, dynamic routes are resolved at build time via
inpaths()[param].paths.ts - Putting
after content in Markdown (must be before any Markdown content for reliable parsing)<script setup> - Using
outside of Vue setup context (it is a composable, not a global function)useData()
Gotchas & Edge Cases:
only processes Markdown files — non-createContentLoader
files matching the glob are silently skipped.md- Data loader files must end in
(or.data.ts
,.data.js
,.data.mts
) — the.data.mjs
suffix is required.data - Frontmatter
acceptsoutline
(array) or[2, 3]
(string) — numbers alone are invalid'deep'
config must start and end withbase
(e.g.,/
) or VitePress will error'/docs/'- Dead links cause build failures by default — use
only temporarily during migrationignoreDeadLinks: true
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md
(You MUST use
with .vitepress/config.ts
for all site configuration)defineConfig()
(You MUST use data loader files (
) for build-time data — never fetch data at runtime in SSR-unsafe ways)*.data.ts
(You MUST handle SSR compatibility — no bare
, window
, or browser APIs outside document
or onMounted
)<ClientOnly>
(You MUST extend the default theme via
and layout slots — do not fork the entire theme)extends: DefaultTheme
(You MUST use
for markdown collection pages — it handles caching, watching, and frontmatter extraction)createContentLoader()
Failure to follow these rules will cause SSR build failures, bloated bundles, and broken upgrades.
</critical_reminders>