Migrating from Docusaurus to Starlight
Docusaurus and Starlight solve the same problem — ready-made documentation sites from Markdown — with different architectures. Docusaurus renders every page with React and then runs the site as a React single-page app in the browser. Starlight renders static HTML with Astro and ships almost no JavaScript unless a page uses an interactive component. For documentation teams whose Core Web Vitals, page weight or build times have become a problem, that architectural difference is the main reason to migrate; for teams deeply invested in Docusaurus versioning or its plugins, it may not be worth it.
This guide covers a complete migration of a 740-page product documentation site: converting content and admonitions, rebuilding the sidebar, replacing MDX components, deciding what to do about versions, keeping URLs, and measuring the result. It is part of Migrating Between Static Site Generators, and the framework comparison behind the decision is in Docusaurus vs Starlight for Product Documentation.
Prerequisites
- An inventory of the Docusaurus site: plugins, swizzled components, custom MDX components, versions, and the full URL list from the built
sitemap.xml. - A Starlight 0.29+ project scaffolded with
npm create astro@latest -- --template starlight. - A redirect mechanism on the host, and a link checker for the new build.
Step 1: Inventory What Docusaurus Was Doing
Most of a migration's surprises come from features nobody remembered were in use. The inventory for this site:
| Feature | Docusaurus | Starlight equivalent |
|---|---|---|
Admonitions (:::note, :::tip, :::caution, :::danger) | built in | asides (:::note, :::tip, :::caution, :::danger) |
Tabs (<Tabs>, <TabItem>) | @theme/Tabs | <Tabs>, <TabItem> from @astrojs/starlight/components |
Sidebar from sidebars.js | built in | sidebar config or autogenerate |
| Versioned docs (4 versions) | built in | none built in |
| Algolia DocSearch | plugin | Pagefind built in, or DocSearch plugin |
| Swizzled footer and navbar | 2 components | component overrides |
| Custom MDX components | 7 React components | Astro components or React islands |
| Blog | plugin | separate collection or starlight-blog |
Seven custom React components, two swizzles and the versioning decision were the real work; everything else was mechanical.
Step 2: Convert Content With a Script
Content lives in docs/ in Docusaurus and in src/content/docs/ in Starlight. A conversion script copies files and applies the mechanical changes, so it can be rerun whenever writers change the source during the migration:
- Frontmatter:
sidebar_positionbecomessidebar.order;sidebar_labelbecomessidebar.label;slugstays if it matches the desired URL;idis dropped (Starlight uses the file path). - Admonition titles: Docusaurus uses
:::note Custom title; Starlight uses:::note[Custom title]. - Imports:
import Tabs from '@theme/Tabs'becomesimport { Tabs, TabItem } from '@astrojs/starlight/components', and<TabItem value="npm" label="npm">loses thevalueattribute. - Images: Docusaurus resolves
/img/…fromstatic/; move images intosrc/assets/and rewrite paths so Astro optimizes them, or intopublic/to keep them as is. - Category pages:
_category_.jsonfiles become sidebar group configuration.
The script handled 11,400 changed lines across 740 files. Running the new build's link checker after each run of the script produced a shrinking list of problems that pointed at gaps in the script rather than at individual pages.
Step 3: Rebuild the Sidebar
Docusaurus sidebars are usually defined in sidebars.js with explicit items and categories. Starlight supports both explicit groups and autogenerated groups from directories. The cleanest migration kept explicit groups for the top level, where order and labels were deliberate, and autogenerated the contents of each group from directories using sidebar.order in frontmatter:
sidebar: [
{ label: 'Get started', items: ['getting-started', 'getting-started/install', 'getting-started/quickstart'] },
{ label: 'Guides', autogenerate: { directory: 'guides' } },
{ label: 'API reference', collapsed: true, autogenerate: { directory: 'api' } }
]
A small script diffed the sidebar order of the old and new builds by scraping both sites' navigation, which found 14 pages in the wrong position before any reader did.
Step 4: Replace React Components
MDX files that import custom React components need a decision per component. Components that render static markup — a feature matrix, a styled card grid, a version badge — were rewritten as Astro components, which output HTML with no JavaScript. Components that are genuinely interactive — an API request builder and a configuration generator — stayed React, rendered in MDX with a client:visible directive after adding @astrojs/react. The swizzled footer and navbar became Starlight component overrides, described in Customizing Starlight Without Forking the Theme.
The effect on JavaScript was large: pages with no interactive component shipped none, and the two interactive pages loaded React only when the component scrolled into view.
Step 5: Decide What to Do About Versions
Docusaurus's versioning was the hardest thing to replace, because Starlight has none built in. The site had four versions; analytics showed 91% of traffic on the current version, 7% on the previous one and 2% combined on the two oldest. The team chose to migrate only the current and previous versions into Starlight — the previous one under /v3/ as a separate content directory with its own sidebar group — and to freeze the two oldest as a final static Docusaurus build served from /archive/v1/ and /archive/v2/ with a banner. Old version URLs redirect to the archive. This removed three quarters of the build's page count and made the new build 5.2 times faster than the old one. Versioned Documentation with Docusaurus discusses the trade-offs of keeping versions at all.
Step 6: Keep URLs Working
Docusaurus's default URL for docs/guides/deploy.md is /docs/guides/deploy, without a trailing slash, while the new site used Starlight at the root with trailing slashes. Rather than keep /docs/, the team moved the documentation to the root and generated redirects for every old URL from the old sitemap: 2,960 URLs including versions, mapped to new URLs by a script and reviewed by hand for the 41 pages whose slugs changed. The redirect map and its tests follow Keeping Redirects Working After an SSG Migration and Testing Redirects and Headers Before Deploy.
Heading anchors deserve a check too: Docusaurus and Starlight both use GitHub-style slugs, and only 3 of about 9,000 heading IDs differed, all headings containing emoji.
Measured Impact
Four weeks after launch, field data from the documentation site's RUM and Search Console:
| Measure | Docusaurus 3.5 | Starlight 0.29 |
|---|---|---|
| Build time (all versions built) | 7 min 40 s | 1 min 28 s |
| JavaScript on a typical page (brotli) | 188 KB | 3 KB |
| LCP p75, mobile, field | 2.6 s | 1.5 s |
| INP p75, field | 210 ms | 64 ms |
| Lighthouse performance, docs template | 71 | 99 |
| 404s from old URLs (Search Console, 4 weeks) | — | 12 |
| Organic clicks, 4 weeks vs previous 4 weeks | — | +6% |
Pitfalls & Rollback
- Assuming MDX carries over unchanged.
@theme/*imports and React components need individual decisions. - Forgetting client-side navigation behaviour. Docusaurus preserved scroll position and sidebar state across navigations; Starlight uses normal page loads, which some readers noticed. View transitions, as in View Transitions on Multi-Page Static Sites, soften the difference.
- Dropping versions without redirects. Old version URLs are heavily linked from issues and forums.
- Search behaviour changes. Pagefind ranks differently from Algolia; test the top 50 queries.
- Rollback: keep the last Docusaurus build deployable on a subdomain during the first weeks; DNS or a host alias switches back in minutes.
Conclusion
Moving from Docusaurus to Starlight is mostly mechanical content conversion plus a handful of real decisions: custom components, swizzled theme parts, versions and search. Script the mechanical part so it can be rerun, decide the rest explicitly, and generate redirects from the old sitemap. On this site the result was a build five times faster, pages that went from 188 KB of JavaScript to 3 KB, and LCP and INP comfortably inside Core Web Vitals thresholds.
FAQ
Why would a team move from Docusaurus to Starlight?
Common reasons are page weight and Core Web Vitals, since Starlight ships almost no JavaScript while Docusaurus ships a React single-page app, faster builds on large sites, and preferring Astro's component model. Teams that rely heavily on Docusaurus versioning or its plugin ecosystem often have fewer reasons to move.
Can MDX content be reused as is?
Mostly. Plain Markdown and most MDX carry over. React components imported in MDX need to be replaced with Astro components or wrapped as React islands, admonition syntax needs small changes, and Docusaurus-specific imports such as @theme components have no equivalent.
How does Starlight handle versioned docs?
Starlight has no built-in versioning. Options are a community plugin, keeping old versions as a separate archived build on a subpath, or maintaining only the current version. Many teams migrating use the move to retire old versions.
Will URLs change?
They can stay the same if Starlight's file structure mirrors Docusaurus's route structure. Differences usually come from Docusaurus slugs set in frontmatter, category index pages and trailing-slash settings, and each needs either a matching slug or a redirect.
Related
- Parent: Migrating Between Static Site Generators — migration practice in general.
- Docusaurus vs Starlight for Product Documentation — deciding whether to move.
- Migrating from MkDocs to Starlight — a similar move from Python-based docs.
- Auditing Content Parity After a Migration — proving nothing was lost.
- Comparing Docs Framework Build Times — build-time context.
Filed underHow-tosDocusaurusStarlight