Migrating from Docusaurus
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:
- Scaffold a Trellis Docs project — follow Getting Started to create a new project
- Have your Docusaurus project accessible — the script reads from its
docs/directory - 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
| Flag | Description |
|---|---|
--dry-run | Preview what would change without writing any files |
--force | Overwrite existing files in content/docs/ |
Always run with --dry-run first to review the changes:
node scripts/migrate-docusaurus.js ../my-docusaurus-site --dry-runThen run without the flag to perform the migration:
node scripts/migrate-docusaurus.js ../my-docusaurus-siteWhat 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
| Transform | Example |
|---|---|
| Numbered prefixes stripped | 01-getting-started.md → getting-started.mdx |
.md renamed to .mdx | guide.md → guide.mdx |
README.md renamed to index.mdx | api/README.md → api/index.mdx |
Frontmatter
Docusaurus-only frontmatter fields are removed automatically:
sidebar_position,sidebar_label,displayed_sidebarid,slug,pagination_label,pagination_next,pagination_prevcustom_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 insidebars.js. When omitted, Docusaurus derives it from the file path. For example, a file atguides/setup.mdwithid: quickstartwould be referenced in the sidebar asguides/quickstart.slug— Controls the URL path independently of theidand file path. A doc atguides/setup.mdwithslug: /get-startedwould 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
idvalues — No action needed. The generatedconfig/sidebar.tsalready references docs by file path, which is the Trellis Docs convention. - Custom
slugvalues — URLs will change. The migration report warns about each affected file. Add entries toredirects.jsonso old links continue to work (see Add redirects below).
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
| Transform | Details |
|---|---|
@theme imports removed | import Tabs from '@theme/Tabs' — Trellis provides these automatically |
@site imports removed | Warns you to verify no custom components are missing |
| HTML comments converted | <!-- comment --> → {/* comment */} (MDX syntax) |
require() image paths converted | require('@site/static/img/foo.png').default → '/img/foo.png' |
| MDX partial imports converted | import Foo from './_foo.mdx'; <Foo /> → @include ./_foo.mdx |
| Excessive blank lines cleaned | Three 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 source | How it's handled |
|---|---|
sidebars.js / .cjs / .json | Loaded and converted directly |
sidebars.ts | Parsed 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 labels | Used for category names in the generated sidebar |
| No sidebar file found | Generated 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.tsAfter migration
1. Review the sidebar
Open config/sidebar.ts and verify the structure matches your expectations. You may want to:
- Reorder items
- Adjust
collapsedvalues - Add
linkproperties 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:
- Copy the source file into
components/ - Rename the extension:
.jsx→.tsxor.js→.ts - Add TypeScript type annotations (at minimum, type the props interface and the return type)
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.
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 buildFix any MDX compilation errors. Common issues:
| Error | Fix |
|---|---|
| JSX expression expected | HTML comments missed by the converter — change <!-- --> to {/* */} |
Security: require() calls are not allowed | A 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 defined | A custom component whose import was stripped but the JSX tag remains — port the component or remove the tag |
| Unknown component | A Docusaurus component (<CodeBlock>, <BrowserWindow>) that needs to be removed or replaced |
| Type error: comparison has no overlap | The SidebarItem type in config/sidebar.ts is missing the link or api variants — add them (see Sidebar type) |
| Expression expected | Bare < or > characters in text — wrap in backticks or escape as < / > |
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}:
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
| Item | Why | What to do |
|---|---|---|
| Blog posts | Different directory structure | Copy from blog/ to content/blog/ manually |
| Custom pages | Docusaurus uses React pages in src/pages/ | Recreate in app/ as Next.js pages |
| Custom CSS | Docusaurus uses src/css/custom.css | Map to design tokens |
| Custom components | Source files are not copied, only detected and reported | Port each component to components/ — see Custom components |
| Plugins | Docusaurus plugins have no Trellis equivalent | Use built-in features or build custom components |
| Versioned docs | Different snapshot format | Re-snapshot with npm run version:snapshot after migration |
| i18n translations | Different directory structure | Copy translated content into content/i18n/<locale>/docs/ |
Syntax compatibility
Most Docusaurus markdown syntax works in Trellis Docs without changes:
| Feature | Docusaurus | Trellis Docs | Status |
|---|---|---|---|
| Admonitions | :::tip | :::tip | Works as-is |
| Custom admonition titles | :::tip Custom Title | :::tip Custom Title | Works as-is |
| Tabs | <Tabs> / <TabItem> | <Tabs> / <TabItem> | Works — no import needed |
| Code blocks | ``` | ``` | Works as-is |
| Frontmatter | YAML | YAML | Works — Docusaurus fields stripped |
| MDX comments | {/* comment */} | {/* comment */} | Works as-is |
| HTML comments | <!-- comment --> | Not supported | Auto-converted by script |
require() image paths | require('@site/static/img/foo.png').default | '/img/foo.png' | Auto-converted by script |
| Partials (import) | import Foo from './_foo.mdx'; <Foo /> | @include ./_foo.mdx | Auto-converted by script (handles props and block form) |
<DocCardList /> | <DocCardList /> | <DocCardList category="..." /> | Same name — pass category prop |
| Image width | Not supported |  | New — set width via title attribute |
| Variables | Not supported | {vars.key} | New feature |