Migrating from Docusaurus

Last updated: 03/04/2026Edit this page

Trellis Docs includes a migration script that automates the bulk of moving from Docusaurus. It copies your content, converts your sidebar, transforms frontmatter, and flags anything that needs manual attention.

Prerequisites

Before running the migration:

  1. Scaffold a Trellis Docs project — follow Getting Started to create a new project
  2. Have your Docusaurus project accessible — the script reads from its docs/ directory
  3. Back up your Docusaurus project — the script only reads from it, but backups are always a good idea

Running the migration

node scripts/migrate-docusaurus.js <path-to-docusaurus-project>

Options

FlagDescription
--dry-runPreview what would change without writing any files
--forceOverwrite existing files in content/docs/

Always run with --dry-run first to review the changes:

node scripts/migrate-docusaurus.js ../my-docusaurus-site --dry-run

Then run without the flag to perform the migration:

node scripts/migrate-docusaurus.js ../my-docusaurus-site

What the script does

The migration runs in four phases.

Phase 0: Custom component scan

Before touching any files, the script walks the Docusaurus project's src/components/ directory to build a list of custom React components. During content processing it checks each migrated MDX file for references to those components and reports them so you know exactly what needs to be ported.

src/theme/ (Docusaurus swizzle overrides) is intentionally excluded — Trellis has its own built-in equivalents for Navbar, Footer, and other swizzleable components, so those overrides don't need to be ported.

The script distinguishes between TypeScript (.ts/.tsx) and JavaScript (.js/.jsx) source files because they require different porting steps — see Custom components below.

Phase 1: Content files

The script copies every .md and .mdx file from the Docusaurus docs/ directory into content/docs/, applying these transforms:

File paths

TransformExample
Numbered prefixes stripped01-getting-started.mdgetting-started.mdx
.md renamed to .mdxguide.mdguide.mdx
README.md renamed to index.mdxapi/README.mdapi/index.mdx

Frontmatter

Docusaurus-only frontmatter fields are removed automatically:

  • sidebar_position, sidebar_label, displayed_sidebar
  • id, slug, pagination_label, pagination_next, pagination_prev
  • custom_edit_url, parse_number_prefixes, hide_title

The tags field is converted to keywords (the Trellis equivalent).

Docusaurus id and slug fields

Docusaurus is unique among docs-as-code platforms in supporting separate id and slug frontmatter fields:

  • id — An internal identifier used to reference the doc in sidebars.js. When omitted, Docusaurus derives it from the file path. For example, a file at guides/setup.md with id: quickstart would be referenced in the sidebar as guides/quickstart.
  • slug — Controls the URL path independently of the id and file path. A doc at guides/setup.md with slug: /get-started would be served at /get-started.

Trellis Docs does not use either field. Instead, the file path is the single source of truth for both sidebar references and URL routing — the same approach used by MkDocs, VitePress, Starlight, and most other docs frameworks.

The migration script strips both fields and maps sidebar entries to file paths. If your Docusaurus project relies on either:

  • Custom id values — No action needed. The generated config/sidebar.ts already references docs by file path, which is the Trellis Docs convention.
  • Custom slug values — URLs will change. The migration report warns about each affected file. Add entries to redirects.json so old links continue to work (see Add redirects below).
Tip

If your Docusaurus sidebars.js references docs by custom id values and the mapping isn't obvious, run the migration with --dry-run first and cross-reference the generated sidebar against your original to verify every doc is accounted for.

Content body

TransformDetails
@theme imports removedimport Tabs from '@theme/Tabs' — Trellis provides these automatically
@site imports removedWarns you to verify no custom components are missing
HTML comments converted<!-- comment -->{/* comment */} (MDX syntax)
require() image paths convertedrequire('@site/static/img/foo.png').default'/img/foo.png'
MDX partial imports convertedimport Foo from './_foo.mdx'; <Foo />@include ./_foo.mdx
Excessive blank lines cleanedThree or more blank lines reduced to two

The MDX partial conversion handles self-closing tags with or without props (<Foo />, <Foo prop="x" />), and block forms including multiline content (<Foo>...</Foo>). Any usage pattern that can't be auto-converted is flagged as a warning.

require() paths under @site/static/ and relative /static/ paths are converted to root-relative URLs. Any require() call that can't be resolved automatically is flagged because next-mdx-remote blocks all require() calls for security.

The script warns about <CodeBlock> components and non-Docusaurus imports that need manual review.

Assets

Non-markdown files (images, PDFs, etc.) in the docs/ directory are copied alongside the content with the same path transforms.

Phase 2: Sidebar conversion

The script reads your Docusaurus sidebar configuration and generates a Trellis Docs config/sidebar.ts file.

Docusaurus sourceHow it's handled
sidebars.js / .cjs / .jsonLoaded and converted directly
sidebars.tsParsed as plain data (TypeScript types stripped)
type: 'autogenerated'Generated from the filesystem after content is copied
type: 'link' (external)Skipped with a warning
_category_.json labelsUsed for category names in the generated sidebar
No sidebar file foundGenerated entirely from the filesystem

If a config/sidebar.ts already exists, the script backs it up to config/sidebar.backup.ts before writing.

Phase 3: Variable suggestions

After migration, the script scans your content for repeated strings — version numbers, product names, and URLs that appear three or more times. These are candidates for content variables in config/variables.ts.

Migration report

After running, the script prints a summary:

========================================
       MIGRATION REPORT
========================================

Files migrated:  42
Files skipped:   0
Assets copied:   8
Errors:          0
Warnings:        3

Frontmatter fields stripped:
  sidebar_position: 38 file(s)
  sidebar_label: 12 file(s)
  id: 5 file(s)

HTML comments converted to MDX: 7
Files renamed (.md -> .mdx): 15
Numbered prefixes stripped: 22

Sidebar: Generated config/sidebar.ts

Custom components detected:
(Each must be ported to components/ or replaced with a Trellis built-in.)

  TypeScript components (copy source to components/, check for API differences):
    <BrowserWindow> — 3 file(s), source: /path/to/src/components/BrowserWindow.tsx
      guides/quickstart.mdx
      guides/advanced.mdx
      reference/api.mdx

  JavaScript components (rename .jsx→.tsx / .js→.ts, then add TypeScript types):
  Note: Trellis tsconfig uses strict:true — type annotations are required.
    <InfoBox> — 1 file(s), source: /path/to/src/components/InfoBox.jsx
      overview/intro.mdx

Suggested variables for config/variables.ts:
  acme_platform: 'Acme Platform' (found 28 times)
  v2_1_0: 'v2.1.0' (found 8 times)

Warnings:
  - guides/api.mdx: Had custom slug "/api-reference" — URL will change
  - guides/advanced.mdx: Stripped 2 @site import(s) — verify no custom components are missing
  - External sidebar link skipped: Community -> https://discord.gg/acme

Next steps:
  1. Review migrated files in content/docs/
  2. Review generated config/sidebar.ts
  3. Run "npm run build" to verify the build succeeds
  4. Check any warnings above and fix manually if needed
  5. Port custom components listed above to your Trellis components/ directory
     or replace them with Trellis built-ins (Callout, Tabs, etc.)
  6. Consider adding suggested variables to config/variables.ts

After migration

1. Review the sidebar

Open config/sidebar.ts and verify the structure matches your expectations. You may want to:

  • Reorder items
  • Adjust collapsed values
  • Add link properties to categories that have index pages

See Configuring the sidebar for the full sidebar API.

2. Port custom components

If the migration report listed custom components, each one needs to be ported to your Trellis project's components/ directory or replaced with a Trellis built-in.

TypeScript components (.tsx / .ts source):

Copy the source file into components/ and verify it compiles. React and Next.js API differences between Docusaurus and Trellis are usually minor, but check for any Docusaurus-specific imports (@docusaurus/*, @theme/*) that need to be removed or replaced.

JavaScript components (.jsx / .js source):

Trellis uses strict: true TypeScript and its tsconfig.json only includes *.ts and *.tsx files. Plain JavaScript components won't compile as-is. For each JS component:

  1. Copy the source file into components/
  2. Rename the extension: .jsx.tsx or .js.ts
  3. Add TypeScript type annotations (at minimum, type the props interface and the return type)
components/InfoBox.tsx
interface InfoBoxProps {
  children: React.ReactNode
  type?: 'info' | 'warning'
}

export function InfoBox({ children, type = 'info' }: InfoBoxProps) {
  // ... original component body
}

Once ported, register the component globally if your MDX pages use it without importing it — see MDX components for how to add global components.

Tip

Many Docusaurus custom components can be replaced entirely with Trellis built-ins: <BrowserWindow><Callout>, <CodeBlock> → fenced code blocks, info boxes → :::note admonitions. Check what the component actually does before porting.

3. Run the build

npm run build

Fix any MDX compilation errors. Common issues:

ErrorFix
JSX expression expectedHTML comments missed by the converter — change <!-- --> to {/* */}
Security: require() calls are not allowedA require() call the script couldn't auto-convert — rewrite as a static image path (/img/foo.png) or Next.js <Image> import
Expected component X to be definedA custom component whose import was stripped but the JSX tag remains — port the component or remove the tag
Unknown componentA Docusaurus component (<CodeBlock>, <BrowserWindow>) that needs to be removed or replaced
Type error: comparison has no overlapThe SidebarItem type in config/sidebar.ts is missing the link or api variants — add them (see Sidebar type)
Expression expectedBare < or > characters in text — wrap in backticks or escape as &lt; / &gt;

5. Update site configuration

Edit config/site.ts with your branding, logo, and URLs. See Site Configuration for all options.

6. Add variables

If the migration report suggested variables, add them to config/variables.ts and replace hardcoded values in your content with {vars.key}:

config/variables.ts
export const docVariables: Record<string, string> = {
  productName: 'Acme Platform',
  version: '2.1.0',
}

7. Set up navigation

Edit config/navigation.ts to configure your top navbar and footer. See Navigation.

8. Add redirects

If your Docusaurus site used custom slugs or numbered prefixes, URLs will have changed. Add entries to redirects.json so old links still work:

[
  { "from": "/api-reference/", "to": "/guides/api/" },
  { "from": "/01-intro/", "to": "/intro/" }
]

See the Redirects guide for details.

What the script does not migrate

ItemWhyWhat to do
Blog postsDifferent directory structureCopy from blog/ to content/blog/ manually
Custom pagesDocusaurus uses React pages in src/pages/Recreate in app/ as Next.js pages
Custom CSSDocusaurus uses src/css/custom.cssMap to design tokens
Custom componentsSource files are not copied, only detected and reportedPort each component to components/ — see Custom components
PluginsDocusaurus plugins have no Trellis equivalentUse built-in features or build custom components
Versioned docsDifferent snapshot formatRe-snapshot with npm run version:snapshot after migration
i18n translationsDifferent directory structureCopy translated content into content/i18n/<locale>/docs/

Syntax compatibility

Most Docusaurus markdown syntax works in Trellis Docs without changes:

FeatureDocusaurusTrellis DocsStatus
Admonitions:::tip:::tipWorks as-is
Custom admonition titles:::tip Custom Title:::tip Custom TitleWorks as-is
Tabs<Tabs> / <TabItem><Tabs> / <TabItem>Works — no import needed
Code blocks``````Works as-is
FrontmatterYAMLYAMLWorks — Docusaurus fields stripped
MDX comments{/* comment */}{/* comment */}Works as-is
HTML comments<!-- comment -->Not supportedAuto-converted by script
require() image pathsrequire('@site/static/img/foo.png').default'/img/foo.png'Auto-converted by script
Partials (import)import Foo from './_foo.mdx'; <Foo />@include ./_foo.mdxAuto-converted by script (handles props and block form)
<DocCardList /><DocCardList /><DocCardList category="..." />Same name — pass category prop
Image widthNot supported![alt](/img/pic.png "50%")New — set width via title attribute
VariablesNot supported{vars.key}New feature

Was this page helpful?