If you need versioned docs, several languages and search all at once, Docusaurus is the only one of the four that ships versioning and i18n as first-party features. You add search with a community local-search plugin, so you never need Algolia. Starlight is the strongest alternative: Pagefind search and i18n work out of the box, but versioning depends on a community plugin. MkDocs Material has the best built-in search of the group and handles versioning through the separate mike tool. VitePress is the lightest and fastest option, but it has no versioning at all.
TL;DR: pick by the feature you cannot live without.
-
Versioned product docs in two or more languages, such as a two-founder API startup shipping v1 and v2 at the same time: Docusaurus, because
docs:versionand the i18n directory structure are core features maintained by the same team. -
A Python or infrastructure team that writes plain Markdown, such as a two-person data tooling company: MkDocs Material, because its built-in client-side search is excellent and
mikecovers versioning without any JavaScript. - A Vue shop documenting a single living version, such as a solo-maintained component library: VitePress, because built-in local search and i18n are enough when you never need to freeze old versions.
- A content-heavy multilingual site that should ship as little JavaScript as possible, such as a startup's public developer portal: Starlight, because Pagefind and Astro's static output keep pages light while i18n stays built in.
- A team that already has a React design system: Docusaurus, because you can import your real components directly into MDX pages.
- Anyone who wants zero third-party services in the request path: any of the four with local search, hosted as static files on infrastructure you control.
The central tradeoff is simple: Docusaurus gives you the most built-in features at the cost of a heavier React build, and the lighter tools ask you to add versioning yourself.
Table of contents
- What do Docusaurus, MkDocs Material, VitePress and Starlight actually ship out of the box?
- Which docs generator handles versioning without hacks?
- How does i18n compare when you publish in two or more languages?
- Can you get good documentation search without paying for Algolia?
- Local search plugins vs self-hosted Typesense or Meilisearch: where each one stops scaling
- How long do builds take as your docs grow from 50 to 1,000 pages?
- Authoring in MDX, Markdown extensions and React, Vue or Astro components
- What does each option really cost a two-person team over 3 years?
- Theming and customisation: how much frontend work will you actually do?
- Will the project still be maintained when you need it in three years?
- How to self-host a static docs site with Docker and Nginx
- Data sovereignty: why self-hosted docs and search matter for a small company
- Which docs generator fits your team profile?
What do Docusaurus, MkDocs Material, VitePress and Starlight actually ship out of the box?
All four generators output static HTML that you can host anywhere. They differ in which of the three features in our title come in the box and which you have to add yourself. Docusaurus runs on React and MDX. MkDocs Material is a Python theme for MkDocs, configured from a single mkdocs.yml. VitePress runs on Vue and Vite. Starlight is a documentation theme for Astro.
| Tool | Built in | Needs a plugin or separate tool |
|---|---|---|
| Docusaurus | Versioning through docusaurus docs:version, i18n with per-locale folders, a blog, autogenerated sidebars, an Algolia DocSearch connector |
Local search, for example the community docusaurus-search-local plugin |
| MkDocs Material | Client-side search plugin, translated UI strings, a blog plugin, admonitions and content tabs | Versioning through mike, multilingual content through a plugin such as mkdocs-static-i18n
|
| VitePress | Local search powered by MiniSearch, i18n with locale config, an Algolia connector | Versioning, which has no official solution |
| Starlight | Pagefind search, i18n with fallback content, sidebar generation, built-in accessibility features | Versioning through the community starlight-versions plugin |
The pattern is clear. Only Docusaurus treats versioning as a core feature. Three of the four ship self-hosted search by default, and Docusaurus is the exception because its official search path points to Algolia. Every tool except VitePress has a reasonable route to all three features. How much each "plugin" entry should worry you depends on who maintains it, and the next sections look at that for each feature.
Which docs generator handles versioning without hacks?
Versioning means you can freeze the docs for v1 while you keep editing the docs for v2, and readers can switch between them from a dropdown. Every option except Docusaurus treats it as an add-on.
-
Docusaurus, built-in snapshots: run
npm run docusaurus docs:version 1.0and the currentdocs/folder is copied toversioned_docs/version-1.0. Its sidebar goes toversioned_sidebars, and the version is added toversions.json. ThedocsVersionDropdownnavbar item gives readers the switcher. The catch is that every snapshot is a full copy, so a fix that applies to three versions has to be made in three places. The Docusaurus docs themselves advise keeping the number of active versions small. -
MkDocs Material with mike, versioned builds:
mike deploy 1.0 latest --update-aliasesbuilds the site and commits the output to a git branch under a/1.0/path, and Material's version selector reads it when you setextra.version.provider: mike. Your source stays a single tree, which avoids duplicated fixes. The deployment model assumes you serve that branch, though, so self-hosting means checking out the branch in your pipeline instead of simply copying asite/folder. - Starlight with starlight-versions, plugin snapshots: the community plugin copies content into per-version folders, much as Docusaurus does, and adds a version picker. It works, but it is maintained by a community contributor rather than the Astro core team, so check its compatibility before every Starlight upgrade.
-
VitePress, no official answer: the usual workaround is to build each version from its own git branch with a different
basepath and link the builds by hand. That is the definition of a hack.
If you will support two or more release lines at the same time, this section alone points you to Docusaurus.
How does i18n compare when you publish in two or more languages?
All four tools can publish a translated site. The differences show up in three places: what happens to pages nobody has translated yet, how UI strings are handled, and how much configuration each new locale adds.
-
Docusaurus, full pipeline with a cost: you declare
localesindocusaurus.config.jsand runnpm run write-translations -- --locale frto generate JSON files for theme strings. Translated docs go underi18n/fr/docusaurus-plugin-content-docs/current/. Missing pages fall back to the default language. By default each locale is built as a separate site, so build time grows with every language you add. Combined with versioning, your translation workload becomes versions multiplied by locales. -
Starlight, the smoothest defaults: set
defaultLocaleandlocalesinastro.config.mjsand put each translation in a folder such assrc/content/docs/fr/. Untranslated pages show the default content with a notice telling readers the page is not translated yet. UI strings ship translated for a long list of languages, so a second locale is mostly a content task. -
VitePress, explicit but manual: the
localesconfig maps folders such as/fr/to languages. Nav, sidebar and labels are declared again for each locale inthemeConfig. There is no automatic fallback, so a missing translation is simply a missing page. -
MkDocs Material, split in two:
theme.languagetranslates the interface, but content translation needs a plugin such asmkdocs-static-i18n, using file suffixes likeindex.fr.mdor folders per language. The language switcher is configured separately throughextra.alternate.
For two languages, any of the four is workable. Beyond three, Starlight's fallback notices and Docusaurus's Crowdin-friendly file layout save the most maintenance.
Can you get good documentation search without paying for Algolia?
Yes. For most small teams, the free self-hosted options are good enough that Algolia is a choice, not a requirement. Algolia's DocSearch program is free, but it only accepts publicly accessible technical documentation that its crawler can reach. Internal docs behind a login, a commercial product that fails the eligibility review, or a site you do not want crawled by a third party all push you towards a paid Algolia plan. They can also push you to the local options below.
- Starlight with Pagefind, the most scalable default: Pagefind runs after the build, indexes the final HTML and splits the index into small fragments. The browser downloads only the fragments that match a query, so search stays light as the site grows. It needs no configuration.
-
MkDocs Material, the most polished default: the built-in
searchplugin runs in a web worker, highlights matches on the target page and suggests completions as you type. You enable it with one line inmkdocs.yml, and for many teams it is the reason to choose Material. -
VitePress with MiniSearch, solid with fuzzy matching: set
themeConfig.search.providerto'local'and you get fuzzy search with a keyboard-friendly modal. The index is built per locale, so each language stays separate. -
Docusaurus with a community plugin, good but external:
@easyops-cn/docusaurus-search-localbuilds a lunr index at build time and supports versioned docs and several languages. It is the most widely used option, but it is not maintained by the Docusaurus team, which matters for upgrades.
All four keep every query in the reader's browser, with no external request and no third-party log of what your users search for.
Local search plugins vs self-hosted Typesense or Meilisearch: where each one stops scaling
Client-side search has no server to run, but the browser does the work, and that work grows with your content. A self-hosted search engine moves the work to a server you maintain. The right choice depends on how much text you index, not on how many visitors you get.
| Option | How it works | Where it stops scaling |
|---|---|---|
| Lunr-based plugins (Docusaurus local search, MkDocs Material) | A single prebuilt index is downloaded and searched in the browser | The index grows with every page, version and locale, so first-search latency rises on large or heavily versioned sites |
| MiniSearch (VitePress) | One index per locale, loaded when the search modal opens | Same shape as lunr: a fine fit for a single-version site, heavier as content grows |
| Pagefind (Starlight, usable with any static site) | A fragmented index, so only the chunks relevant to a query are fetched | Scales to very large sites, because the download depends on the query rather than on the total size of the site |
| Typesense, self-hosted | A server process with a crawler that indexes your live site, plus docusaurus-theme-search-typesense for the frontend |
Scales well beyond any client-side option, but you now run, update and back up a database |
| Meilisearch, self-hosted | A server process fed by a scraper, queried through a DocSearch-style search bar | Same tradeoff: typo tolerance and relevance tuning in exchange for an always-on service |
For a two-person team, the cutover point is not the number of pages. It is the moment when versions multiplied by locales make the client-side index noticeably slow. Pagefind pushes that point the furthest without adding a server.
If you do run Typesense or Meilisearch, it is one more container next to your docs, on a self-managed VPS, a home server, a NAS or Yundera. Yundera is a managed Personal Cloud Server, built on CasaOS, that runs self-hosted apps as Docker containers on a server dedicated to the user.
How long do builds take as your docs grow from 50 to 1,000 pages?
At 50 pages, every tool here builds quickly enough that it does not matter. At 1,000 pages with versions and locales, build time decides how often you can afford to deploy. We will not quote benchmark figures, because results depend heavily on your content, plugins and CI hardware. The scaling patterns are consistent, though, and you can measure your own case with time npm run build or time mkdocs build.
-
Docusaurus, the heaviest by design: every version and every locale is a full set of pages run through a React bundler. Ten versions in three languages can mean around thirty times the pages of a single version. Docusaurus 3.6 introduced the Docusaurus Faster options, enabled through the
future.experimental_fasterconfig flag, which swap in Rspack, SWC and Lightning CSS. Turn them on before you judge Docusaurus on build time. -
MkDocs Material, simple but single-threaded: MkDocs rebuilds the whole site on each change, and Python plugins run one after another. Image-heavy features such as the social cards plugin add noticeable time.
mkdocs serve --dirtyspeeds up local editing by rebuilding only the files that changed. - VitePress, the fastest feedback loop: the Vite dev server compiles pages on demand, so local editing stays fast as the site grows. Production builds go through Rollup and are consistently light, partly because there is no versioning multiplier.
- Starlight, fast with one extra step: Astro builds static HTML efficiently, then Pagefind indexes the output. Astro's image optimisation is often the slowest part of the build, and it is cached between builds when you keep the cache directory in CI.
The practical rule: cost grows with pages multiplied by versions multiplied by locales, and only Docusaurus multiplies all three by default.
Authoring in MDX, Markdown extensions and React, Vue or Astro components
Your writers will spend far more time in the content files than in the config. How portable those files are decides how painful a future migration will be.
| Tool | Content format | Components and gotchas |
|---|---|---|
| Docusaurus | MDX 3 for .mdx files, admonitions written as :::note
|
You can import any React component, including your product's real ones. MDX 3 is strict: a stray { or < in prose breaks the build. Setting markdown.format: 'detect' parses plain .md files as CommonMark |
| MkDocs Material | Python-Markdown with the pymdownx extensions, admonitions written as !!! note, tabs as === "Tab"
|
No component model. You reuse content through snippets (--8<--) or Jinja templates from mkdocs-macros-plugin. Indentation-based syntax catches out writers coming from GitHub Markdown |
| VitePress | markdown-it with custom containers such as ::: tip and ::: code-group
|
Vue components work directly inside .md files with no MDX step. Line highlighting such as {1,3} is built into code fences |
| Starlight | Markdown, MDX and optional Markdoc, asides written as :::note
|
Ships <Tabs>, <Card> and <Steps> from @astrojs/starlight/components. Through Astro islands you can use React, Vue or Svelte components, and Expressive Code handles code blocks |
Two things matter most for a small team. First, admonition and tab syntax is different in every tool, so these are the lines you will rewrite during a migration. Keep them few. Second, components are a commitment: a docs page full of React imports only moves to another React-capable tool. If plain Markdown covers most of your pages, Starlight and VitePress keep the most portable files, and MkDocs Material is portable as long as you avoid heavy pymdownx syntax.
What does each option really cost a two-person team over 3 years?
Licence cost is not a factor: Docusaurus, MkDocs Material, VitePress and Starlight are all MIT-licensed and free. Over three years, your money and time go to four other places, and the size of each one depends far more on your choices than on the tool.
- Hosting, close to zero: the output is static files. They fit on a server you already pay for, next to your other containers, or on a free static host. No tool here needs a database or an application runtime in production, so the hosting line looks the same for all four.
- Search, zero or a recurring bill: Pagefind, MkDocs Material's built-in search, MiniSearch and the Docusaurus local plugin cost nothing. Algolia is free only through DocSearch for eligible public docs, and is otherwise a usage-based plan that grows with records and searches. A self-hosted Typesense or Meilisearch instance has no licence fee but needs memory on an always-on server and your time to keep it updated.
- CI minutes, proportional to build time: this is where the versions-multiplied-by-locales effect from earlier turns into money. If you build on a hosted CI with a minute quota, a heavily versioned multilingual Docusaurus site uses that quota fastest. Building on your own server removes the meter.
-
Engineer hours, the real cost: major upgrades are where the time goes. The Docusaurus move from version 2 to 3 required many teams to fix MDX 3 syntax errors across their content. Community plugins such as
starlight-versionsordocusaurus-search-localadd their own upgrade checks. Mike-based versioning adds a deployment branch to maintain.
For a two-person startup, the honest three-year figure is a few days of upgrade work per major release, plus whatever paid search you choose not to avoid.
Theming and customisation: how much frontend work will you actually do?
Every tool here looks professional with zero design work. The question is what happens when you need your brand colours, a custom header or a product-specific widget, and how much of that survives an upgrade.
-
MkDocs Material, the least frontend skill: colours come from
theme.paletteinmkdocs.yml, extra styles fromextra_css, and layout changes from Jinja templates in acustom_dirsuch asoverrides/main.html, where you override named blocks. You never touch a JavaScript framework, which suits a backend-heavy team. The ceiling is low, though, for anything interactive. -
Starlight, targeted overrides: brand colours are CSS variables such as
--sl-color-accent, loaded through thecustomCssoption. To replace a part of the UI, you list it under thecomponentsoption and point it at your own.astrofile, for example a customHeader. The override surface is documented and narrow, so upgrades rarely break it. -
VitePress, slots before forks: you extend the default theme in
.vitepress/theme/index.tsand inject content through layout slots such asdoc-beforeornav-bar-content-after. Colours are variables like--vp-c-brand-1. If that is not enough, you can write a completely custom Vue theme, which is real frontend work. -
Docusaurus, maximum power, maximum risk: Infima variables such as
--ifm-color-primaryinsrc/css/custom.csscover colours. For structural changes,npm run swizzle @docusaurus/theme-classic Footer -- --ejectcopies a React component into your project. The CLI labels components as safe or unsafe to swizzle, and ejected unsafe components are the most common cause of broken Docusaurus upgrades. Prefer--wrapwhen you can.
If nobody on your team writes frontend code, start with MkDocs Material or Starlight.
Will the project still be maintained when you need it in three years?
Static output means your site keeps working even if a project dies. What you lose is security updates for the build toolchain, compatibility with new Node or Python releases, and a migration you did not plan for. Judge each tool by who maintains it and whose own docs depend on it.
- Docusaurus, corporate backing: Meta Open Source maintains it, and a long list of major projects build their documentation with it. It has a steady release cadence and a documented upgrade path for each major version. The risk is the usual one with corporate open source: priorities are set by Meta, not by you.
- VitePress, dogfooded by its maintainers: it lives in the Vue organisation and powers the docs of Vue, Vite and Vitest. As long as those projects need documentation, VitePress gets maintained. Version 1.0 shipped in 2024, and work on version 2 is under way.
- Starlight, core team ownership: it is maintained inside the Astro organisation rather than by a lone contributor, and Astro's own docs run on it. Its releases follow Astro's major versions, so plan an upgrade whenever Astro moves.
- MkDocs Material, a successor in progress: its team has announced that Material is moving to maintenance mode while it builds Zensical, a successor designed to read existing MkDocs projects. Your Material site keeps working, but a three-year plan should include an evaluation of Zensical.
-
Community plugins, the real weak spot:
starlight-versions,docusaurus-search-localandmkdocs-static-i18neach depend on a small number of maintainers. Before you rely on one, check the date of its last release and whether it supports the current major version of its host.
The core tools are the safe part. Your plugin list is where the long-term risk sits.
How to self-host a static docs site with Docker and Nginx
All four generators produce a folder of static files, so the hosting pattern is the same: build in one container stage, serve with Nginx in a second one. The final image contains no Node or Python runtime, which keeps it small and gives it less to patch.
-
A multi-stage Dockerfile: the first stage uses
node:22-alpineand runsnpm ci && npm run build. For MkDocs Material, use a Python image withpip install mkdocs-materialand runmkdocs build. The second stage startsFROM nginx:alpineand copies the output to/usr/share/nginx/html. The output folders differ:build/for Docusaurus,site/for MkDocs,docs/.vitepress/distfor VitePress anddist/for Starlight. -
Clean URLs in Nginx: add
try_files $uri $uri/ $uri.html =404;to thelocation /block. Without it, VitePress withcleanUrlsenabled and Docusaurus withtrailingSlash: falsereturn 404 errors on deep links. Pointerror_page 404at the generated404.html. -
Cache headers that match hashed assets: fingerprinted files under
/assets/(Docusaurus, VitePress) and/_astro/(Starlight) can be cached for a year withimmutable. Keep HTML on a short cache so new deployments show up immediately. Pagefind's/pagefind/index files change on every build, so give them a short cache as well. -
mike needs an extra step: because mike writes versions to a git branch, your build stage runs
git archive gh-pagesor checks out that branch, instead of runningmkdocs build. - TLS and placement: put a reverse proxy such as Caddy or Traefik in front for HTTPS. The container then runs on a self-managed VPS, a home server, a NAS or Yundera, and on Yundera the app gets a public HTTPS subdomain with no port forwarding.
The whole image typically rebuilds and redeploys in one CI job.
Data sovereignty: why self-hosted docs and search matter for a small company
Docs look like public marketing content, but they leak more than you think. Search queries reveal what customers struggle with, internal runbooks describe your infrastructure, and every third-party script on a page tells another company who reads it. A static site with local search lets you keep all of that on hardware you choose.
Advantages of keeping docs and search in-house:
- Private search queries: with Pagefind, MiniSearch or a lunr index, queries never leave the reader's browser. With hosted search, every query goes to the search provider.
- Internal docs without a crawler: DocSearch needs public pages. Self-hosted docs can sit behind basic auth or a forward-auth proxy such as oauth2-proxy and still have full search.
-
No silent third-party requests: MkDocs Material loads Google Fonts by default. Its built-in
privacyplugin downloads external assets at build time and serves them from your own domain. - A hosting location you choose: putting your server in a specific country makes GDPR answers short and checkable.
Self-hosting checklist:
- Search: local index or self-hosted engine, no hosted search key in your config.
- Fonts and scripts: inspect the browser's network tab on a built page and confirm every request goes to your own domain.
- Analytics: none, or a self-hosted tool such as Plausible or Umami.
- Access control: decide which sections are public before launch, not after.
- Hosting: a VPS in a region you picked, a home server, a NAS, or Yundera, where data stays on your own server rather than in a shared multi-tenant service.
-
Source of truth: Markdown in git, so any host can be rebuilt from one
git clone.
Which docs generator fits your team profile?
| Profile | Recommendation | Main reason |
|---|---|---|
| API startup supporting v1 and v2 at the same time | Docusaurus | Versioning is a core feature, not a plugin |
| Team with a React design system to show in docs | Docusaurus | Real components import directly into MDX |
| Python or DevOps team that writes plain Markdown | MkDocs Material | Excellent built-in search and no frontend code |
| Multilingual public portal with a single living version | Starlight | Built-in i18n fallbacks plus Pagefind |
| Vue library with one current version | VitePress | Fastest editing loop and Vue components in Markdown |
| Internal runbooks behind a login | Starlight or MkDocs Material | Local search works without public crawling |
| Large site expected to pass 1,000 pages | Starlight | Pagefind's fragmented index scales best client-side |
| Team with no frontend developer | MkDocs Material | Config and Jinja overrides cover most needs |
Next steps:
Docusaurus:
- Enable
future.experimental_fasterfrom day one. - Install
@easyops-cn/docusaurus-search-localand test it with your versioned docs. - Run your first
docs:versiononly when a release actually needs freezing.
MkDocs Material:
- Enable the
searchandprivacyplugins inmkdocs.yml. - Add
mikeonly once you have a second release line. - Note Zensical in your roadmap for a later evaluation.
Starlight:
- Set
defaultLocalebefore you write any content. - Install
starlight-versionsonly once you need a frozen release. - Override components through the
componentsoption instead of patching the theme.
VitePress:
- Set
themeConfig.search.providerto'local'. - Confirm that you will never need versioned docs, or plan for branch-based builds.
Top comments (0)