web-meta-framework-vitepress-040a7f — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited web-meta-framework-vitepress-040a7f (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Quick Guide: VitePress is a Vue-powered static site generator for documentation, configured entirely in.vitepress/config.ts. The sidebar is either an array (one sidebar everywhere) or an object keyed by URL path prefix (a sidebar per section). Data loaders — files ending.data.ts— run at build time and ship only their serialized result to the client. Vue components work directly inside Markdown through<script setup>. Every page is pre-rendered at build time, which is the constraint behind most of the red flags below.
>
Version: VitePress 1.6.x, on Vite 6+ and Vue 3.5+.
Detailed Resources:
<critical_requirements>
Put site configuration in `.vitepress/config.ts` and wrap it in `defineConfig()`. That path is where the build looks, and the wrapper is what gives the option names type checking.
*Load data through a `.data.ts` loader rather than fetching in a component.** The loader runs at build time and the client receives only the serialized result, which is both faster and SSR-safe.
Guard browser APIs with `<ClientOnly>` or `onMounted`. Every page is pre-rendered at build time, so a bare window or document reference crashes the build rather than the browser.
Extend the default theme with `extends: DefaultTheme` plus layout slots. A fork is a copy that stops tracking upstream, and the slot list is broad enough that forking is rarely the shorter route.
Build Markdown collection pages with `createContentLoader()`. It already handles the glob, the frontmatter extraction, mtime caching and dev-mode watching.
</critical_requirements>
Auto-detection: VitePress, vitepress, .vitepress/config, defineConfig vitepress, createContentLoader, vitepress/theme, DefaultTheme, useData, useSidebar, markdown-it plugin vitepress, vitepress deploy
Applies to:
.vitepress/config.tscreateContentLoader for Markdown collections, custom load() for anything elsetransformPageData, transformHead, transformHtml, buildEndHandled elsewhere:
<patterns>
One file, wrapped for type checking. cleanUrls drops .html from URLs, sitemap.hostname generates sitemap.xml, lastUpdated reads git timestamps, and search.provider: "local" is search with no service to sign up for.
import { defineConfig } from "vitepress";
export default defineConfig({
title: "My Docs",
cleanUrls: true,
lastUpdated: true,
sitemap: { hostname: "https://docs.example.com" },
themeConfig: {
nav: [{ text: "Guide", link: "/guide/" }],
sidebar: {
/* Pattern 2 */
},
search: { provider: "local" },
editLink: { pattern: "https://github.com/org/repo/edit/main/docs/:path" },
},
});Full code: examples/core.md
An array gives one sidebar for the whole site. An object keyed by path prefix gives a different sidebar per section, and the first matching prefix wins.
sidebar: {
"/guide/": [
{ text: "Getting Started", collapsed: false, items: [{ text: "Introduction", link: "/guide/introduction" }] },
{ text: "Advanced", collapsed: true, items: [{ text: "Data Loaders", link: "/guide/data-loading" }] },
],
"/api/": [{ text: "API Reference", items: [{ text: "Config", link: "/api/config" }] }],
}The trailing slash matters: /guide also matches /guidelines. Omitting collapsed makes a group permanently expanded rather than collapsible.
Full code: examples/core.md
A *.data.ts file runs at build time and exports data. createContentLoader covers Markdown collections; a plain object with watch and load() covers everything else.
// posts.data.ts
import { createContentLoader } from "vitepress";
export default createContentLoader("blog/posts/*.md", {
excerpt: true,
transform: (raw) =>
raw
.sort(
(a, b) => +new Date(b.frontmatter.date) - +new Date(a.frontmatter.date),
)
.map(({ url, frontmatter, excerpt }) => ({
title: frontmatter.title,
url,
excerpt,
})),
});<script setup>
import { data as posts } from "./posts.data";
</script>transform is where you drop what the client does not need, which is why includeSrc and render are opt-in.
Full code: examples/core.md
<script setup> at the top of a .md file makes its imports available to that page, and page-scoped imports code-split. Register a component globally only when many pages use it.
<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
export default {
extends: DefaultTheme,
enhanceApp({ app }) {
app.component("StatusBadge", StatusBadge);
},
};Full code: examples/core.md
Wrap DefaultTheme's Layout and fill its named slots. The slot list is long enough that most customization needs no fork.
<script setup>
import DefaultTheme from "vitepress/theme";
const { Layout } = DefaultTheme;
</script>
<template>
<Layout>
<template #doc-before><div class="author-banner">...</div></template>
<template #doc-footer-before
><div class="feedback-widget">...</div></template
>
</Layout>
</template>The full slot list is in reference.md.
Full code: examples/core.md
Four hooks in config, running in order: transformPageData per page during render, transformHead per page after render, transformHtml per page on the final HTML string, and buildEnd once, for generating extra files into siteConfig.outDir.
export default defineConfig({
transformPageData(pageData) {
pageData.frontmatter.head ??= [];
pageData.frontmatter.head.push([
"meta",
{ property: "og:title", content: pageData.title },
]);
},
async buildEnd(siteConfig) {
const posts = await createContentLoader("blog/*.md").load();
// write an RSS feed or redirect map into siteConfig.outDir
},
});Full code: examples/core.md
Containers, tabbed code groups, line highlighting and annotations, file snippets and partial includes, all on top of standard Markdown.
::: tip RECOMMENDATION
Containers are `info`, `tip`, `warning`, `danger` and `details`; the word after the type is the title.
:::
::: code-group
export default defineConfig({ title: "Docs" });
export default { title: "Docs" };
:::
<<< @/snippets/example.ts
<!--@include: ./shared/header.md-->Line ranges highlight with a brace suffix on the language (ts{2-3}), and in-code annotations are // [!code focus], // [!code ++], // [!code --], // [!code warning] and // [!code error].
Full code: examples/core.md
markdown.config receives the fully-configured markdown-it instance, with VitePress's own plugins already registered — which is why plugins go here rather than into an instance of your own.
export default defineConfig({
markdown: {
lineNumbers: true,
toc: { level: [1, 2, 3] },
config: (md) => {
md.use(markdownItFootnote);
},
},
});Full code: examples/core.md
</patterns>
<red_flags>
Breaks at runtime:
window, document or a browser-only library reached outside onMounted or <ClientOnly> — the build pre-renders every page and crashes there..vitepress/config.ts — it is not found, and nothing reports that it was looked for.base without a leading and trailing slash ("/docs/") — VitePress errors..data.ts, .data.js, .data.mts or .data.mjs — the .data suffix is what makes it a loader.ignoreDeadLinks: true is a migration crutch rather than a setting to keep.useData() called outside a Vue setup context — it is a composable, not a global.outline given a bare number where the field takes [2, 3] or 'deep'.Surprising behaviour:
render: true on a large createContentLoader collection puts every page's full HTML in the client bundle."/guide" also matches /guidelines).cleanUrls: true, every URL carries .html.sitemap without hostname generates a sitemap of empty URLs, and lastUpdated needs both the config flag and real git history — in CI that means an unshallow checkout.<script setup> placed after Markdown content parses unreliably — put it first.paths() in a [param].paths.ts file; they are not request-time routes.--base at build time.createContentLoader silently skips non-Markdown files that match its glob.</red_flags>
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.