All skills
antfu avatar

/vitepress

@d02c484 official
by Anthony Fuantfu/skills5.9k stars
335

VitePress static site generator powered by Vite and Vue. Use when building documentation sites, configuring themes, or writing Markdown with Vue components.

Use this Skill: https://skilld.dev/gh/antfu/skills/vitepress

This session only. Nothing lands on disk.

referencesadvanced-ssr.md

≈1k tokens on demand. Your agent reads this file only when SKILL.md points to it.

SSR Compatibility

VitePress pre-renders pages on the server during build. All Vue code must be SSR-compatible.

The Rule

Only access browser/DOM APIs in Vue lifecycle hooks:

  • onMounted()
  • onBeforeMount()
<script setup>
import { onMounted, ref } from 'vue'

const windowWidth = ref(0)

onMounted(() => {
  // Safe - runs only in browser
  windowWidth.value = window.innerWidth
})
</script>

Do NOT access at top level:

<script setup>
// WRONG - runs during SSR where window doesn't exist
const width = window.innerWidth
</script>

ClientOnly Component

Wrap non-SSR-friendly components:

<template>
  <ClientOnly>
    <BrowserOnlyComponent />
  </ClientOnly>
</template>

Libraries That Access Browser on Import

Some libraries access window or document when imported:

Dynamic Import in onMounted

<script setup>
import { onMounted } from 'vue'

onMounted(async () => {
  const lib = await import('browser-only-library')
  lib.doSomething()
})
</script>

Conditional Import

if (!import.meta.env.SSR) {
  const lib = await import('browser-only-library')
  lib.doSomething()
}

In enhanceApp

// .vitepress/theme/index.ts
export default {
  async enhanceApp({ app }) {
    if (!import.meta.env.SSR) {
      const plugin = await import('browser-plugin')
      app.use(plugin.default)
    }
  }
}

defineClientComponent

Helper for components that access browser on import:

<script setup>
import { defineClientComponent } from 'vitepress'

const BrowserComponent = defineClientComponent(() => {
  return import('browser-only-component')
})
</script>

<template>
  <BrowserComponent />
</template>

With props and slots:

<script setup>
import { ref, h } from 'vue'
import { defineClientComponent } from 'vitepress'

const componentRef = ref(null)

const BrowserComponent = defineClientComponent(
  () => import('browser-only-component'),
  // Props passed to h()
  [
    { ref: componentRef, someProp: 'value' },
    {
      default: () => 'Default slot content',
      header: () => h('div', 'Header slot')
    }
  ],
  // Callback after component loads
  () => {
    console.log('Component loaded', componentRef.value)
  }
)
</script>

Teleports

Teleport to body only with SSG:

<ClientOnly>
  <Teleport to="body">
    <div class="modal">Modal content</div>
  </Teleport>
</ClientOnly>

For other targets, use postRender hook:

// .vitepress/config.ts
export default {
  async postRender(context) {
    // Inject teleport content into final HTML
  }
}

Common SSR Errors

"window is not defined"

Code accesses window at module level:

// BAD
const width = window.innerWidth

// GOOD
let width: number
onMounted(() => {
  width = window.innerWidth
})

"document is not defined"

Same issue with document:

// BAD
const el = document.querySelector('#app')

// GOOD
onMounted(() => {
  const el = document.querySelector('#app')
})

Hydration Mismatch

Server and client render different content:

<!-- BAD - different on server vs client -->
<div>{{ typeof window !== 'undefined' ? 'client' : 'server' }}</div>

<!-- GOOD - consistent -->
<ClientOnly>
  <div>Client only content</div>
</ClientOnly>

Checking Environment

// In Vue component
import.meta.env.SSR  // true on server, false on client

// In VitePress
import { inBrowser } from 'vitepress'
if (inBrowser) {
  // Client-only code
}

Key Points

  • Access browser APIs only in onMounted or onBeforeMount
  • Use <ClientOnly> for non-SSR components
  • Use defineClientComponent for libraries that access browser on import
  • Check import.meta.env.SSR for environment-specific code
  • Teleport to body only, or use postRender hook
  • Consistent rendering prevents hydration mismatches
<!-- Source references: - https://vitepress.dev/guide/ssr-compat -->

Source: SKILL.md on GitHub

No alerts2d5 checks · Risk SAFE
  • Gen Agent Trust Hub2d

    The skill provides comprehensive documentation and configuration examples for VitePress, a static site generator. No malicious patterns or intent were detected. A minor surface for indirect prompt injection was identified due to the documented features for ingesting external data during the site build process, which is inherent to the framework's functionality.

  • Socket2d

    No alerts

  • Snyk2d

    Risk: LOW · No issues

  • Runlayer7mo

    2/16 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at d02c484. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 days ago.

Activeupdated 4 days ago
Other metadata
metadata
{
  "author": "Anthony Fu",
  "version": "2026.9.25",
  "source": "Generated from https://github.com/vuejs/vitepress, scripts located at https://github.com/antfu/skills"
}

README badge

README badge for antfu/skills/vitepress

VitePress is a static site generator built on Vite and Vue 3 that converts Markdown files into a fast single-page application, with file-based routing and built-in support for Vue components directly in Markdown. Use it for documentation sites, blogs, and marketing pages where you need configurable themes, syntax-highlighted code blocks, and instant hot-reload during development.

Generated from the current SKILL.md.

Does VitePress work with Vue components embedded in Markdown?
Yes. Vue components work directly in Markdown files, and you can use script setup and directives within Markdown content.
Can I build a multi-language documentation site with VitePress?
Yes. VitePress includes internationalization support with locale configuration for building multi-language sites.
What search options does VitePress provide?
VitePress includes built-in local search or integration with Algolia for full-text search across documentation.
Can I customize the default theme or build a custom one from scratch?
Yes. You can extend the default theme via CSS variables and slots, or build a completely custom theme by implementing the theme interface.
Does VitePress support dynamic route generation?
Yes. You can generate pages from data at build time using createContentLoader and paths loader files for dynamic routing.

Generated from the current SKILL.md. These answers refresh after source changes.