Skip to content

vitepress-plugin-llms

Publishes LLM-friendly artifacts for a VitePress site: an llmstxt.org index (/llms.txt), a single-file full-text dump of all pages (/llms-full.txt) and a per-page mirror of the source Markdown (<url>.md). A tool that downloads /foo/index.md receives the source, not the VitePress HTML wrapper.

Useful when documentation needs to be consumed by search-index robots (GEO), assistants such as ChatGPT/Claude and internal RAG pipelines: the VitePress HTML render is redundant and noisy for them.

Installation

bash
pnpm add @ampernic/vitepress-plugin-llms

If the package is installed from Forgejo instead of npmjs, also add the scope to .npmrc:

@ampernic:registry=https://altlinux.space/api/packages/ampernic/npm/

Usage

The plugin returns three VitePress config hooks, wire them into defineConfig at the root:

ts
// .vitepress/config.ts
import { defineConfig } from 'vitepress'
import { LlmsPlugin } from '@ampernic/vitepress-plugin-llms'

const llms = LlmsPlugin({
  hostname: 'https://docs.altlinux.space',
  title: 'ALT Linux Documentation',
  description: 'Reference documentation for ALT distributions.',
  section: (p) => p.url.split('/').filter(Boolean)[0] ?? 'root',
  filter: (p) => !p.url.endsWith('/404.html'),
})

export default defineConfig({
  transformPageData: llms.transformPageData,
  buildEnd: llms.buildEnd,
  vite: llms.vite,
})

Through createSharedConfig in @ampernic/vitepress-theme-alt-docs these hooks are wired automatically; options go under the llms field.

In dev mode vite.plugins adds middleware that serves .md with Content-Type: text/markdown; charset=utf-8, so browsers and external clients do not miss the MIME type.

Options

OptionTypeDefaultDescription
hostnamestring--Absolute origin for absoluteUrl fields in llms.txt / llms-full.txt. Without it URLs stay root-relative
titlestring'Documentation'Title of llms.txt and llms-full.txt. Defaults to the root page frontmatter (altHome.hero.title or title)
descriptionstring''One-line description right under the title. Fallback also comes from the root frontmatter
fallbackSummarystring''Summary of an llms.txt entry when the page has no first paragraph
emitIndexbooleantrueWrite llms.txt
emitFullTextbooleantrueWrite llms-full.txt
emitPerPageMarkdownbooleantruePlace a <url>.md mirror next to every HTML page
filter(page: PageMeta) => boolean--Removes pages (auto-generated indexes, redirects) from every artifact
section(page: PageMeta) => stringfirst URL segmentGrouping key in the llms.txt tree
pageTitle(page: PageMeta) => stringfrontmatter title | pageData.titleRenames an index entry
summary(page: PageMeta) => stringfirst paragraph of the sourceOne-line summary of the page in llms.txt
renderSource(page: PageMeta) => string | undefined--Rewrites what goes into artifacts and per-page .md. Needed to expand frontmatter-driven landings (<DocSections />) into real Markdown for LLM
fullTextMaxBytesnumber8 * 1024 * 1024Hard limit on llms-full.txt size. Older LLM clients choke on multi-megabyte files. 0 means no limit
preamblestring--Extra Markdown block in the header of llms.txt: contacts, license, project description
extraPages() => SyntheticPage[]--Synthetic pages appended to the set before emit. Useful for aggregator sites that list pages they do not host themselves

What goes into the artifacts

  • llms.txt: title + description + optional preamble + a flat list of sectionless entries + entries grouped by section(page) with links and a one-line summary of each page.
  • llms-full.txt: title + description + one block per page, --- separator, url: / title: header. Order is lexicographic by URL so the diff between builds is deterministic.
  • Per-page .md: raw Markdown from the repo. For <url>/ VitePress returns <url>index.md, for <url> it returns <url>.md.

Notes on the output

  • renderSource runs before filter. When the root landing is rendered from frontmatter, returning full Markdown from it is the only way to give the LLM something meaningful.
  • Title and description of llms.txt resolve as: plugin options > altHome.hero.title / altHome.hero.text of the root page > its title / description > defaults. On a per-distribution site the "root" is ${base} (for example, /alt-workstation/), not /.
  • Without hostname entry URLs are root-relative. GEO indexers often require absolute links, so it is better to always set it.
  • The per-page .md is written to outDir without the site.base prefix: on deploy the distribution already sits under /<distro>/, and doubling the prefix would give a 404.