Skip to content
Live demo of ondocs on Starlight. ondocs.vercel.app · GitHub

VitePress

The ondocs/vitepress plugin adds ondocs to a VitePress site by wrapping its config. After every vitepress build it indexes the pages the site serves, writes llms.txt and the pages’ Markdown copies, and adds the dialog to every page with a floating “Ask AI” button. It works with VitePress 1 and the VitePress 2 alphas, with the default theme or a theme of your own, and needs no theme file.

Live demo: these docs as a VitePress site, with the plugin, on Vercel.

The dialog is a React component, so install React and its two dependencies along with the AI SDK. They load with the dialog, the first time a visitor opens it, and never on the rest of your Vue pages:

Terminal window
npm i ondocs ai @ai-sdk/openai react react-dom @radix-ui/react-dialog cmdk

To add ondocs to VitePress, wrap the config in withOndocs:

.vitepress/config.mts
import { defineConfig } from 'vitepress';
import { withOndocs } from 'ondocs/vitepress';
export default withOndocs(
defineConfig({
title: 'Acme Docs',
// ...the rest of your config, as it is
}),
);

Options go in a second argument: withOndocs(defineConfig({ ... }), { endpoint: 'https://ask.acme.dev/api/ask' }). Your config’s own buildEnd, transformHtml and Vite plugins keep working: withOndocs adds its own after them.

After vitepress build, the index is written to .vitepress/dist/ask-index.json, or docs/.vitepress/dist/ask-index.json when the site is in docs/. It is at the URLs VitePress serves: under base, with or without .html as cleanUrls has it, at the paths rewrites give the pages, and with a page for each path of a dynamic route. Files in srcExclude are not pages, so they are not indexed, and neither are pages with search: false in their frontmatter, which VitePress’s local search leaves out too, or with <meta name="robots" content="noindex">. To keep a site out of search engines and still index it, send an X-Robots-Tag: noindex header from the host rather than the meta tag.

With cleanUrls, the index and citations have /guide/setup, and the host must serve guide/setup.html there. On Vercel, set "cleanUrls": true in vercel.json. Without it, VitePress moves between pages without loading them, so the site looks fine, but a page loaded at its URL is a 404: a citation opened in a new tab, a shared link, a reload.

The plugin reads each page’s Markdown as the default theme renders it, the .vp-doc element, without the navigation, sidebar, outline, edit link or pagination, and without the heading anchors and code blocks’ language labels. A home page with only a hero has no Markdown, so it is skipped. A theme of your own that does not use .vp-doc names its content with content: '.my-content' (a tag, #id, .class or [attribute], or a comma-separated list), and ignore: '.toc' leaves parts out. A page’s title is its <h1>, else its title without the site’s, and its description is its frontmatter description. Unchanged pages reuse their vectors from the previous build, cached in node_modules/.cache/ondocs.

The plugin adds a floating “Ask AI” button and opens the dialog with ⌘I or Ctrl+I, so ⌘K stays with VitePress’s search. The button takes the default theme’s colors, and the button and the dialog follow VitePress’s light and dark modes, as its appearance switch sets them. A page loads only the button, about 3 KB of JavaScript, and the dialog loads the first time it is wanted. The plugin adds the button through VitePress’s own client entry, so there is no theme file to write. With mpa: true, VitePress ships no client entry, and pages get no button: add the script tag to head instead.

Pick another key with dialog: { shortcut: 'j' }, turn the shortcut off with shortcut: false, or hide the button with launcher: false. dialog: { links: 'sources' } keeps only the answer’s links to its source pages.

dialog: { navbar: true } puts the “Ask AI” button in the default theme’s nav bar instead of the corner: after the search box, or where it would be without search, in the brand color and with the shortcut on it, ⌘I on a Mac and Ctrl I elsewhere. On a phone it is an icon that stays in the nav bar, not in the menu. Pass a string for another label, as in navbar: 'Ask the docs'; by default it is labels.launcher, in each locale. The floating button is off with it, unless you also set launcher: true.

export default withOndocs(defineConfig({ title: 'Acme Docs' }), { dialog: { navbar: true } });

The plugin adds the button to the theme’s nav bar search as Vite builds it, so there is still no theme file to write. A theme of your own, or one that replaces the nav bar, gets no button from the option: give a button the data-ondocs-open attribute, in a layout slot such as nav-bar-content-after, and it opens the dialog. See Open it from your own button.

Each locale in VitePress’s locales gets an index of its own, with the pages under its key: .vitepress/dist/ask-index.json for the root locale and .vitepress/dist/fr/ask-index.json for fr, served at /fr/ask-index.json. The llms files are written per locale the same way, titled after the locale’s title and description.

On a locale’s pages, the dialog takes the locale from the page’s path and sends it with each question, and the endpoint answers from that locale’s index; the endpoints npx ondocs init writes read the locales from the config. VitePress moves between pages without loading them, and when a visitor switches language, the dialog switches with them. It is named after the locale’s title, and dialog.locales, by the locale’s key, gives it its words in each language: withOndocs(config, { dialog: { locales: { fr: { title: 'Demander à Acme', labels: { launcher: 'Demander' } } } } }). Every label is listed in The ask dialog.

Embeddings work as in the other plugins. OpenAI’s text-embedding-3-small at 512 dimensions is the default when OPENAI_API_KEY is set at build time, or the same model through AI Gateway with AI_GATEWAY_API_KEY; without either key, the index is keyword-only and the build prints a warning. Choose the model with embedding and dimensions, for example { embedding: 'openai:text-embedding-3-small', dimensions: 512 }, embedding: 'workers-ai:@cf/baai/bge-small-en-v1.5' for the Workers AI Worker, or embedding: 'mock' to build without a key.

A VitePress site is static, so the endpoint is a function on its host. Write it with npx ondocs init, which reads .vitepress/dist/ask-index.json on Vercel, Netlify or Cloudflare, or writes a Cloudflare Worker for GitHub Pages; see Deploying. When the endpoint is not at /api/ask on the site, point the dialog at it with endpoint; on another origin, it must send CORS headers for the site’s origin (see Cross-origin requests). To serve the same index to agents, init writes the MCP endpoint too, and the mcp option puts its URL in llms.txt.

llms.txt links pages absolutely when the plugin knows the site’s URL: sitemap.hostname, or the siteUrl option. When vitepress-plugin-llms is in your Vite plugins, the plugin leaves the llms files to it.

To try it locally, build the site once, run npx ondocs dev, which answers from .vitepress/dist/ask-index.json or docs/.vitepress/dist/ask-index.json, and start vitepress dev with ASK_ENDPOINT=http://localhost:8787/api/ask.

Option Default What it does
endpoint ASK_ENDPOINT, else /api/ask Where the dialog posts questions; ASK_ENDPOINT overrides it.
embedding, dimensions OpenAI when a key is set The embedding model, named as the CLI names it.
embeddingModel, embeddingProviderOptions none An AI SDK model object and its options, instead of embedding.
chunking { maxChars: 1200, overlap: 150 } Chunk size and overlap, in characters.
content, ignore .vp-doc What to read from each page, and what to skip besides heading anchors and code labels.
indexFile ask-index.json Where the index is written in .vitepress/dist/ and served from.
exclude [] Path prefixes to leave out, relative to base and the locale.
dialog title, placeholder, suggestions, shortcut, launcher, navbar, theme, labels, locales.
siteUrl sitemap.hostname The site’s URL, for absolute links in llms.txt and an mcp path.
mcp none The MCP endpoint’s URL or path, which llms.txt points agents at.
llmsTxt on Write llms.txt, llms-full.txt and .md copies; false turns them off.