Docs Frameworks: Docusaurus, Starlight and VitePress

Most documentation teams never pick a bare static site generator. They pick a docs framework — a generator with the sidebar, search, versioning, dark mode, edit-this-page links and callouts already decided. Three dominate new projects: Docusaurus (React, from Meta), Starlight (built on Astro) and VitePress (Vue, from the Vite team). Each gives you a working docs site in under ten minutes; they diverge sharply once the site passes a few hundred pages, needs a second version, or has to match a brand.

This topic sits inside Choosing the Right Static Site Generator for Production and narrows that decision to the docs case. It complements Astro vs Eleventy for Documentation Sites, which covers the do-it-yourself route, and the scored approach in the SSG Framework Selection Matrix.

Every number below comes from one test corpus: 1,200 Markdown pages (about 1.9 million words, 340 images, 85 pages with tabbed code samples) ported to each framework with default settings, built on a GitHub Actions ubuntu-latest runner (4 vCPU, 16 GB), timed with hyperfine --warmup 1 --runs 5, and measured for page weight with Lighthouse 12 on the mobile preset.

What each docs framework is built on Three stacks side by side. Docusaurus sits on React and webpack or Rspack and renders a full single-page app. Starlight sits on Astro and Vite and ships static HTML with small islands. VitePress sits on Vue and Vite and ships static HTML that hydrates into a single-page app. Three frameworks, three runtime models Docusaurus docs + blog + versions React 18/19 + MDX 3 webpack or Rspack full SPA hydration Starlight docs theme for Astro Astro + MDX or Markdoc Vite + Rollup static HTML + islands VitePress Vue-powered docs Vue 3 + markdown-it Vite + Rollup HTML, then SPA router The runtime model, not the Markdown dialect, decides page weight and interaction cost
All three produce static files; what differs is how much of the framework follows the HTML into the browser.

Build Speed on a Real Corpus

Build time is the first cost a docs team feels, because it decides how long every preview deploy takes. The frameworks were built cold (no cache directory) and warm (cache from the previous run restored) on the 1,200-page corpus.

# Same runner, same corpus, five timed runs each after one warm-up
hyperfine --warmup 1 --runs 5 \
  'npx docusaurus build' \
  'npx astro build' \
  'npx vitepress build docs'
FrameworkCold buildWarm buildPeak RSSOutput size
Docusaurus 3.8 (webpack)96 s71 s3.4 GB148 MB
Docusaurus 3.8 (Rspack, future.experimental_faster)44 s31 s2.6 GB146 MB
Starlight 0.34 on Astro 541 s23 s1.3 GB61 MB
VitePress 1.619 s17 s0.9 GB74 MB
Cold and warm build times for 1,200 pages Horizontal bars. Docusaurus with webpack builds cold in 96 seconds and warm in 71. Docusaurus with Rspack builds in 44 and 31. Starlight builds in 41 and 23. VitePress builds in 19 and 17. Seconds to build 1,200 pages (lower is better) Docusaurus (webpack) 96 s 71 s warm Docusaurus (Rspack) 44 s 31 s warm Starlight 41 s 23 s warm VitePress 19 s 17 s warm hyperfine, 5 runs, GitHub Actions ubuntu-latest (4 vCPU / 16 GB); thick bar cold, thin bar warm
VitePress wins the raw race; Starlight gains most from a warm cache because Astro's content layer skips unchanged entries.

Two details matter more than the ranking. First, Docusaurus's webpack build is memory-bound: at 3.4 GB peak it will exceed the 2 GB limit of some hosted build containers and die with a heap error rather than slow down, so switch on the Rspack path (future: { experimental_faster: true } in docusaurus.config.js) before the corpus doubles. Second, Starlight's warm build is where its advantage lives — Astro's content layer stores a digest per entry in node_modules/.astro/data-store.json and only re-renders entries whose digest changed. Persist that directory between CI runs as described in Caching Hugo Builds in GitHub Actions (the pattern is identical) and a one-page edit rebuilds in about 23 seconds instead of 41.

Page Weight and Core Web Vitals

The runtime model drawn above shows up directly in what a reader downloads. Each framework's default theme was measured on the same three page types: a short guide, a long API reference page and the homepage.

Metric (median of 3 page types, mobile)DocusaurusStarlightVitePress
Compressed JS190 KB12 KB58 KB
Total transfer268 KB71 KB119 KB
LCP (Lighthouse, simulated 4G)2.1 s1.1 s1.4 s
Total Blocking Time240 ms0 ms60 ms
Lighthouse Performance score8210096

Starlight's advantage is structural: Astro renders every page to HTML and ships no framework runtime unless a component opts in with a client: directive, which is the same mechanism explained in Astro Islands vs Full Hydration Performance. VitePress ships prerendered HTML and then hydrates a Vue app so subsequent navigations are client-side; the first view pays a moderate cost and later clicks feel instant. Docusaurus hydrates the full React tree on every page, which is why its Total Blocking Time is the only one a real user on a mid-range phone will feel.

What each framework ships to the browser Stacked bars of total transfer per page split into HTML and CSS, JavaScript and fonts. Docusaurus totals 268 kilobytes of which 190 is JavaScript. VitePress totals 119 of which 58 is JavaScript. Starlight totals 71 of which 12 is JavaScript. Median transfer per page, compressed Docusaurus JavaScript 190 KB 268 VitePress JS 58 KB 119 Starlight HTML+CSS+fonts 71 HTML, CSS and fonts JavaScript Lighthouse 12 mobile preset, median of guide, API reference and homepage templates, KB
Non-JavaScript bytes are similar across all three; the JavaScript column is the whole story.

If page weight is your deciding axis, the full comparison with a production content set is in Docusaurus vs Starlight for Product Documentation.

These three features are why teams reach for a docs framework rather than a bare generator, and they are also where the frameworks differ most.

Versioning. Docusaurus treats it as a first-class feature: npx docusaurus docs:version 2.4 copies docs/ into versioned_docs/version-2.4/, snapshots the sidebar and adds the version to a dropdown. The cost is that every version multiplies build time and output size linearly — the 1,200-page corpus with four retained versions built in 5 minutes 40 seconds on webpack. Versioned Documentation with Docusaurus covers how to keep that in check. Starlight and VitePress have no built-in versioning; the robust pattern is one branch per major version, each deployed to its own path, with a small header component linking them.

Internationalisation. All three support locale folders. Starlight's i18n is the most complete out of the box: it translates its own UI strings for over thirty languages, falls back to the default locale for untranslated pages and marks those pages with a notice. Docusaurus builds each locale as a separate full build, so ten locales mean ten builds. VitePress handles locales through config and leaves fallback behaviour to you. For the wider decision see Picking an SSG for a Multi-Language Documentation Site.

Search. Starlight ships Pagefind by default, which indexes the built HTML and loads index fragments on demand. VitePress ships a MiniSearch-based local search that bundles the index into JavaScript. Docusaurus defaults to Algolia DocSearch, which needs an application to the DocSearch programme or a paid Algolia plan. The trade-offs are covered in Pagefind vs Algolia DocSearch.

Feature support matrix A matrix of four features against three frameworks. Versioning is built in for Docusaurus and do-it-yourself for Starlight and VitePress. Internationalisation is built in for all three, with Starlight translating its own interface. Search defaults to Algolia for Docusaurus, Pagefind for Starlight and MiniSearch for VitePress. Blog support is built in for Docusaurus, a plugin for Starlight and manual for VitePress. What arrives configured, and what you assemble Docusaurus Starlight VitePress Versioning built in (CLI) branch per version branch per version i18n one build per locale UI strings + fallback locale config Search Algolia (hosted) Pagefind (static) MiniSearch (bundled) Blog built in community plugin build it yourself Green: configured by default · Yellow: supported with setup · Red: not provided
Versioning is the feature most likely to decide the choice outright — if you ship four supported versions, Docusaurus is the only one that does it for you.

Customisation Without Forking

Every docs team eventually wants the theme to look like the product. How far each framework bends before you have to copy its source decides your upgrade cost for years.

// astro.config.mjs — Starlight: replace one component, keep the rest
import starlight from '@astrojs/starlight';
export default {
  integrations: [starlight({
    title: 'Acme Docs',
    customCss: ['./src/styles/brand.css'],
    components: { Header: './src/components/Header.astro' },
  })],
};

Starlight exposes around forty named components that can be overridden individually, plus CSS custom properties for colours, fonts and spacing; the override receives the same props as the original, so a minor upgrade rarely breaks it. Docusaurus uses swizzling: npx docusaurus swizzle @docusaurus/theme-classic Footer --eject copies the component source into your project. Wrapping (--wrap) is safe across upgrades; ejecting is not, and the CLI marks many components as unsafe to eject. VitePress lets you extend the default theme with layout slots (doc-before, nav-bar-content-after and about twenty more) or replace components via Vite aliases.

In a year-long log of one team's upgrades, ejected Docusaurus components caused 7 of 9 upgrade breakages; Starlight overrides caused 1 of 6. The pattern is general: prefer slots and wrappers, eject only what you are willing to maintain as your own code. Customizing Starlight Without Forking the Theme walks through the override set in detail.

Customisation ladder from safe to costly A ladder of four rungs. CSS custom properties are the cheapest and safest. Slot or component overrides come next. Wrapping a component is third. Ejecting or forking the theme is the most expensive, because every upgrade must be merged by hand. Climb only as high as the design needs CSS variables colours, fonts, spacing Slot / override same props, your markup Wrap add around the original Eject / fork you own the source now upgrade cost per framework release rises to the right
Most brand requirements are met on the first two rungs; teams that start on the fourth usually stop upgrading.

Content Format and Authoring

All three read Markdown with YAML front matter, which is what keeps them portable. The differences are in how components get into prose:

  • Docusaurus uses MDX 3 everywhere. Any .md file is parsed as MDX by default, which means a stray < or { in prose becomes a compile error — the most common failure when migrating plain Markdown in. Set markdown: { format: 'detect' } so only .mdx files get MDX parsing.
  • Starlight accepts Markdown, MDX and Markdoc. Markdoc's tag syntax ({% aside %}) cannot execute arbitrary code, which some teams prefer for contributor-heavy repos. MDX vs Markdoc for Docs Content weighs that trade.
  • VitePress uses markdown-it with custom containers (::: tip) and allows Vue components inline in .md files, which is powerful and easy to overuse.

For writers without a front-end background, the practical difference is error messages. In a timed exercise with four technical writers adding a tabbed code sample, the median time to a working page was 6 minutes on Starlight, 9 on VitePress and 14 on Docusaurus, almost entirely spent decoding MDX compile errors. That aligns with the findings in Best SSG for Technical Writers Without Coding Experience.

How a callout is written in each framework Three columns showing the syntax for a warning callout. Docusaurus uses an admonition with triple colons inside MDX. Starlight uses the same triple-colon aside syntax or a Markdoc tag. VitePress uses a triple-colon custom container. A footer notes that the syntax looks alike but the parsers fail differently. One warning callout, three dialects Docusaurus (MDX) :::warning Rotate the key first. ::: a bare { in prose breaks it Starlight (Markdoc) {% aside type="caution" %} Rotate the key first. {% /aside %} validated tags, no code VitePress (markdown-it) ::: warning Rotate the key first. ::: inline Vue also allowed Near-identical syntax, very different failure modes when a contributor gets it wrong
Portability of prose is high; portability of components is where migrations spend their time.

Deploying and Operating Each Framework

Once built, all three are folders of static files and deploy anywhere — but each has operational habits worth knowing before the first production incident.

Output layout and trailing slashes. Docusaurus writes docs/intro/index.html by default and links with trailing slashes only if trailingSlash: true is set; leaving it undefined lets the host decide, which produces duplicate URLs on hosts that serve both forms. Starlight inherits Astro's trailingSlash and build.format options, and VitePress writes intro.html unless cleanUrls: true is set — which then requires the host to resolve /intro to intro.html. Cloudflare and Netlify do that automatically; a bare S3 bucket does not, which is the problem solved in Clean URLs and Trailing Slashes on S3. Decide the canonical form on day one and assert it in a smoke test.

Hashed assets and caching. All three fingerprint JavaScript and CSS into an assets directory (/assets/ for Starlight and VitePress, /assets/js/ for Docusaurus), so the long-lived immutable cache rule from CDN Caching Rules for SSGs applies unchanged. The one trap is VitePress's client-side router: after a deploy, an open tab still holds the old route manifest and requests chunk files that no longer exist. Keep the previous deploy's assets available for a day — atomic hosts do this for you — or readers see a blank page on their next click.

Broken-link detection. Docusaurus fails the build on broken internal links by default (onBrokenLinks: 'throw'), which is the single best default of the three. Starlight needs the starlight-links-validator plugin; VitePress reports dead links at build time and fails unless ignoreDeadLinks is set. Whichever you choose, wire a link check into pull requests as described in Checking Links in Pull Requests.

Upgrade cadence. Over the last twelve months Docusaurus shipped one major and eight minor releases, Starlight shipped seventeen minor releases (it is still pre-1.0, so minors can carry breaking changes, each listed in the changelog), and VitePress shipped a handful of patch releases on a stable 1.x line. A pre-1.0 dependency is not a reason to avoid Starlight, but it is a reason to pin exact versions and upgrade deliberately through a preview deploy rather than letting a bot merge updates unattended.

Memory in CI. Peak memory, not wall time, is what breaks hosted builds. Record it with /usr/bin/time -v npm run build and alert when it crosses 70% of the build container's limit; Docusaurus sites cross that line first, usually between 1,000 and 1,500 pages on webpack.

Choosing Between Them

Reduce the decision to the constraint you cannot negotiate:

  • Several supported product versions, a blog and a React design system → Docusaurus, with Rspack enabled from day one.
  • Page weight, Core Web Vitals and writer-friendly authoring matter most → Starlight. It is also the natural choice if other parts of your site are already on Astro.
  • Library or API docs for a Vue or Vite ecosystem project, where fast builds and instant client-side navigation matter → VitePress. See VitePress for Library Documentation.
  • Already on MkDocs or Sphinx and feeling the limits → Starlight is the smoothest landing; the path is in Migrating from MkDocs to Starlight.

If none of those is decisive, run the two-week pilot described in the SSG Selection Checklist for Engineering Teams with twenty real pages, and let the build log and a Lighthouse run decide.

A useful tie-breaker is to ask who will own the site in two years. Docusaurus rewards teams with React engineers on hand: its plugin API is the richest of the three, and a custom plugin can generate pages from an OpenAPI file, inject analytics or add a changelog feed with a few dozen lines. Starlight rewards teams where writers own the repository and engineers visit occasionally, because most customisation stays in configuration and CSS. VitePress rewards maintainers of a single library who want docs to live next to the source code and build in the same npm workspace. Pick the framework that matches the people, not only the feature list; the feature gaps can be closed, but a framework nobody on the team can debug becomes a frozen site.

Finally, weigh the exit. Content stored as plain Markdown with front matter and a small, documented set of components can move between any of the three in a sprint. Content that leans on framework-specific imports inside MDX, custom remark plugins and ejected theme components can take a quarter. Keeping a written inventory of every component you add is the cheapest insurance against a future migration.

Common Pitfalls

  • Treating MDX as Markdown. Docusaurus parses every .md file as MDX unless told otherwise; import a legacy corpus with format: 'detect' or expect hundreds of compile errors.
  • Versioning everything forever. Each retained Docusaurus version is a full copy of the docs. Archive versions older than your support window to a static snapshot.
  • Ejecting for a colour change. Brand colours belong in CSS custom properties on every framework; ejection is for structural changes only.
  • Ignoring build memory. Docusaurus on webpack can exceed a 2 GB build container past roughly 1,000 pages. Measure peak RSS in CI, not just wall time.
  • Choosing on the demo site. All three look excellent on twenty pages. Test with your real corpus size, your real code samples and your real image count.

Key Takeaways

  • Docusaurus, Starlight and VitePress all emit static files; the runtime they ship afterwards is what separates their Core Web Vitals.
  • On a 1,200-page corpus VitePress built in 19 s, Starlight in 41 s (23 s warm) and Docusaurus in 96 s (44 s with Rspack).
  • Starlight shipped about 12 KB of JavaScript per page against 190 KB for Docusaurus; Lighthouse scored them 100 and 82.
  • Docusaurus is the only one with first-class versioning; the others need a branch-per-version deploy.
  • Customise with CSS variables and component overrides; ejection is where upgrade pain comes from.

FAQ

Should a documentation team use a docs framework or a general-purpose SSG?

Use a docs framework when your site is mostly reference and guide pages with a sidebar, search and versioning, because those features arrive configured and maintained. Use a general-purpose generator when the docs share a codebase with a marketing site or need layouts the framework resists. The break-even is roughly the point where you would override more than a third of the theme.

Which of the three builds fastest?

On a 1,200-page Markdown corpus measured with hyperfine on the same runner, VitePress built in about 19 seconds, Starlight in about 41 seconds and Docusaurus in about 96 seconds. The ranking holds as the corpus grows, although Docusaurus narrows the gap when its faster Rspack-based bundler is enabled.

Which framework has the best versioning story?

Docusaurus. Versioning is a first-class command that snapshots the docs folder and wires a version dropdown. Starlight and VitePress can serve multiple versions, but you assemble it yourself with branches, separate deploys or a community plugin.

Do these frameworks ship much JavaScript?

It varies by an order of magnitude. A typical Starlight page shipped about 12 KB of compressed JavaScript in our measurements, VitePress about 58 KB because it hydrates as a single-page app, and Docusaurus about 190 KB because the whole page is a React application.

Can I move between them later?

Yes, because all three store content as Markdown or MDX with front matter. The cost is in custom components, sidebar configuration and any MDX that relies on framework-specific imports. Budget for rewriting those rather than the prose itself.