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:

FeatureDocusaurusStarlight equivalent
Admonitions (:::note, :::tip, :::caution, :::danger)built inasides (:::note, :::tip, :::caution, :::danger)
Tabs (<Tabs>, <TabItem>)@theme/Tabs<Tabs>, <TabItem> from @astrojs/starlight/components
Sidebar from sidebars.jsbuilt insidebar config or autogenerate
Versioned docs (4 versions)built innone built in
Algolia DocSearchpluginPagefind built in, or DocSearch plugin
Swizzled footer and navbar2 componentscomponent overrides
Custom MDX components7 React componentsAstro components or React islands
Blogpluginseparate collection or starlight-blog

Seven custom React components, two swizzles and the versioning decision were the real work; everything else was mechanical.

Migration items split by effort Two panels. Mechanical work handled by a script covered admonitions, frontmatter keys, tabs imports, image paths and internal links across 740 pages. Real work needing decisions covered seven custom React components, two swizzled theme components, four documentation versions and the search provider. Mechanical versus real work Mechanical (scripted) admonition titles and syntax frontmatter key renames tabs imports image and link paths 740 pages, 1 script run Real decisions 7 custom React components 2 swizzled theme parts 4 doc versions search provider about 70% of the effort
The conversion script handled about 95% of changed lines; the remaining 5% took most of the time.

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_position becomes sidebar.order; sidebar_label becomes sidebar.label; slug stays if it matches the desired URL; id is dropped (Starlight uses the file path).
  • Admonition titles: Docusaurus uses :::note Custom title; Starlight uses :::note[Custom title].
  • Imports: import Tabs from '@theme/Tabs' becomes import { Tabs, TabItem } from '@astrojs/starlight/components', and <TabItem value="npm" label="npm"> loses the value attribute.
  • Images: Docusaurus resolves /img/… from static/; move images into src/assets/ and rewrite paths so Astro optimizes them, or into public/ to keep them as is.
  • Category pages: _category_.json files 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.

JavaScript on first load before and after the migration Paired bars of brotli-compressed JavaScript on first load. A typical guide page shipped 188 kilobytes on Docusaurus and 3 kilobytes on Starlight. The API request builder page shipped 231 kilobytes on Docusaurus and 58 kilobytes on Starlight, with React loaded when the component became visible. JavaScript shipped per page Docusaurus Starlight Typical guide page 188 KB 3 KB API request builder page 231 KB 58 KB
From the Lighthouse network requests table, median of five runs per page.

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.

Redirect generation for the migration The old build's sitemap and analytics top pages produce 2,960 old URLs. A script maps each to its new URL by path, with 41 slug changes reviewed by hand and old versions mapped to the archive. The map generates host redirect rules and test cases. Tests run against the preview and every old URL must reach a 200. Mapping every old URL Old URLs sitemap + analytics, 2,960 Map by path 41 reviewed by hand Archive versions /archive/v1/ and v2/ Redirect rules generated for the host Tested on preview every URL reaches 200
Analytics added 84 URLs that were not in the sitemap, mostly old pages still linked from forums.

Measured Impact

Four weeks after launch, field data from the documentation site's RUM and Search Console:

MeasureDocusaurus 3.5Starlight 0.29
Build time (all versions built)7 min 40 s1 min 28 s
JavaScript on a typical page (brotli)188 KB3 KB
LCP p75, mobile, field2.6 s1.5 s
INP p75, field210 ms64 ms
Lighthouse performance, docs template7199
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.

Filed underHow-tosDocusaurusStarlight