CLI and TypeScript
|
This content was generated with the assistance of AI and should be verified against the official documentation of each tool before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
This page was written against Docusaurus 3.10.2 (the last release of the 3.x line) with TypeScript 6.0.3, Node.js
v24.21.0 and npm 11.19.0, and checked on 2026-10-26. It describes the docusaurus command line, one command at a time,
and the way a Docusaurus site is written in TypeScript. The running example is the documentation site of Pocket Units,
a tiny units-conversion library with a C# and a Python implementation. A site generated with
create-docusaurus@3.10.2 classic --typescript was used as the test bed: every command below was run in it, except
where a sentence says otherwise, and the outputs shown are the ones it printed.
The docusaurus executable and the npm scripts
The @docusaurus/core package installs one executable, docusaurus. A generated site wraps each command in an npm
script of the same name, so npm run build and npx docusaurus build do the same thing. docusaurus --version prints
the installed version:
npx docusaurus --version
3.10.2
Source: Docusaurus docs: CLI
Running docusaurus --help lists these commands, and docusaurus <command> --help lists the options of one of them:
| Command | Purpose |
|---|---|
|
Starts the development server with hot reload. |
|
Compiles the site into static files, by default in |
|
Serves a previously built folder with a small static web server. |
|
Builds the site and pushes the result to a GitHub Pages branch. |
|
Copies or wraps a theme component into |
|
Removes build artifacts and caches. |
|
Extracts the translatable strings of the site into JSON files. |
|
Adds explicit heading ids to Markdown files. |
|
Freezes the current docs as a numbered version. |
Three conventions apply to all of them. The optional last positional argument siteDir is the folder that holds the
site (the one containing the configuration file); it defaults to the current directory. The --config <path> option
points to another configuration file. And the help text of --config still says that the default is
[siteDir]/docusaurus.config.js, but the loader looks for several file names in order: docusaurus.config.ts, .mts,
.cts, .js, .mjs and .cjs, and uses the first one that exists. A missing file given with --config fails with
a clear message:
npx docusaurus build --config nonexist.ts
[ERROR] Error: Config file at ".../cli-and-typescript/nonexist.ts" not found.
Source: Docusaurus docs: CLI
The next figure shows what each command reads and writes. Only the generated folders and the files that the commands create for you are drawn.
start
docusaurus start builds the site in development mode and serves it with hot reloading on port 3000. In the 3.10 test
site, which enables future.v4 (and therefore the faster bundler), the server reports Rspack as its bundler:
npx docusaurus start --no-open --port 3124
[INFO] Starting the development server...
[SUCCESS] Docusaurus website is running at: http://localhost:3124/
client (Rspack 1.7.12) compiled successfully
Source: Docusaurus docs: CLI
A request for /docs/pocket-units on that server answered with HTTP 200. The options, taken from --help and the
official reference, are:
| Option | Default | Meaning |
|---|---|---|
|
3000 |
Port of the development server. |
|
localhost |
Host to bind. Use |
|
the default locale |
Starts the site in one locale. |
|
false |
Does not fall back to a full page refresh when hot reload fails. |
|
false |
Does not open a browser tab on start. |
|
false |
Polls for changes instead of using file watchers, optionally every given number of milliseconds. It is meant for environments where watching does not work. |
|
false |
Does not minify the JavaScript bundles. |
|
see above |
Path to the configuration file. |
The official reference adds two caveats: some features, such as anchor links, behave differently in development than in
a production build, so always check the output of build before publishing, and HTTPS in development is enabled with
the environment variables HTTPS=true, SSL_CRT_FILE and SSL_KEY_FILE (the guide suggests creating a local
certificate with mkcert). The HTTPS setup was not executed here.
build
docusaurus build produces the production site: the Markdown and React sources are compiled, every route is rendered
to static HTML, and the result is written to a folder. It reads the future flags of the configuration, so the same
command runs Webpack or Rspack depending on the site (see
Docusaurus Faster and v4 readiness).
npx docusaurus build
[INFO] [en] Creating an optimized production build...
[SUCCESS] Generated static files in "build".
[INFO] Use `npm run serve` command to test your build locally.
Source: Docusaurus docs: CLI
The options are:
| Option | Meaning |
|---|---|
|
Output folder, relative to the workspace. The default is |
|
One or more locales to build. Without it every locale of |
|
Does not minimize the JavaScript bundles. |
|
Builds in development mode, which keeps the full React error messages. In the test site a normal build produced a
folder of 2148 KB and |
|
Visualizes the size of the bundler output as an interactive tree map. The option starts an analyzer page, so it was not executed here. |
|
Path to the configuration file. |
Two environment variables tune the build, according to the official reference (they were not exercised here):
USE_SIMPLE_CSS_MINIFIER=true selects the default cssnano preset instead of the more aggressive one, which helps when
the optimized CSS is broken, and SKIP_HTML_MINIFICATION=true skips the minification of the generated HTML.
Building locales
On a site with two locales, build builds each in turn, and writes the non-default locale in a sub-folder named after it.
This is the output with locales: ['en', 'fr'] and build/fr as the second target:
[INFO] [en] Creating an optimized production build...
[SUCCESS] Generated static files in "build".
[INFO] [fr] Creating an optimized production build...
[SUCCESS] Generated static files in "build/fr".
[INFO] Use `npm run serve` command to test your build locally.
Source: Docusaurus docs: CLI
With npx docusaurus build -l en only the first block is printed and build/fr is not produced. The effect of locales
on URLs and folders is covered in Internationalization.
When a build fails
A build is the place where invalid content stops the pipeline. Two failures seen while testing this page:
-
An unknown field in the configuration is rejected before any compilation:
[ERROR] Error: These field(s) ("tagLine",) are not recognized in docusaurus.config.ts. If you still want these fields to be in your configuration, put them in the "customFields" field. See https://docusaurus.io/docs/api/docusaurus-config/#customfields -
A page that MDX cannot compile fails the client bundle with
Client bundle compiled with errors therefore further build is impossible.followed by a cause such asCould not parse expression with acorn. This is what an old{#id}heading id produced in the test site, as described underwrite-heading-idsbelow.
Source: Docusaurus docs: CLI
serve
docusaurus serve serves an existing build folder with a static server so that you can test the production output
before publishing it. It does not rebuild unless asked:
npx docusaurus serve --no-open --port 3123
[SUCCESS] Serving "build" directory at: http://localhost:3123/
Source: Docusaurus docs: CLI
A request for /docs/pocket-units answered 200 and a request for /nope answered 404. The options are -p, --port
(default 3000), -h, --host (default localhost), --dir <dir> (the folder to serve, default build), --build
(builds first), --no-open (the default is false locally and true in CI) and --config. Running
serve --build --no-open on the two-locale site built both locales and then served them, and /fr/docs/pocket-units
answered 200.
clear
docusaurus clear removes the generated artifacts. The official reference recommends it after upgrading Docusaurus
and before reporting a bug, and it is the first thing to try when the site behaves strangely after a dependency change.
The command printed:
npx docusaurus clear
[SUCCESS] Removed the build output folder at "build".
[SUCCESS] Removed the generated folder at ".docusaurus".
[SUCCESS] Removed the bundler persistent cache folder at "node_modules/.cache".
Source: Docusaurus docs: CLI
It takes only siteDir. It removes the default output folder, so a build written with --out-dir dist-test was still
there afterwards and has to be deleted by hand.
swizzle
Swizzling copies or wraps a component of a theme into src/theme, where your version takes precedence. The concepts,
the safe and unsafe components and the styling side are explained in
Styling, theming and swizzling; this section
covers the command itself. Its form is docusaurus swizzle [themeName] [componentName] [siteDir]. Without arguments it
asks questions interactively. The options are:
| Option | Meaning |
|---|---|
|
Creates a wrapper that renders the original component, so you can add elements before or after it. |
|
Copies the full source of the component, so you can replace its UI and logic. |
|
Lists the available components of a theme without prompting for anything else. |
|
Copies the TypeScript files when the theme provides them. |
|
Copies the JavaScript files. |
|
Allows swizzling components that the theme marks as unsafe without asking for confirmation. |
|
Path to the configuration file. |
The official reference documents --list, --eject, --wrap, --danger, --typescript and --config; the short
forms and --javascript come from --help.
Listing components and safety levels
Listing the components of @docusaurus/theme-classic prints a table with a Wrap column and an Eject column. In the
installed version each cell says Safe, Unsafe or Forbidden. A few rows of the output, reduced to the component name and
the two cells:
npx docusaurus swizzle @docusaurus/theme-classic --list --typescript
DocSidebar Safe Unsafe
Footer Safe Safe
ColorModeToggle Safe Safe
AnnouncementBar Unsafe Unsafe
MDXComponents Forbidden Safe
Blog Forbidden Forbidden
Source: Docusaurus docs: CLI
Without a language option, --list first asks whether to list the JavaScript or the TypeScript files, which blocks
scripts and CI, so pass --typescript or --javascript together with it.
Wrapping and ejecting
Wrapping the footer in TypeScript and ejecting a color-mode toggle produced these results:
npx docusaurus swizzle @docusaurus/theme-classic Footer --wrap --typescript
npx docusaurus swizzle @docusaurus/theme-classic ColorModeToggle --eject --typescript
[SUCCESS]
Created wrapper of Footer from @docusaurus/theme-classic in
- ".../src/theme/Footer/index.tsx"
[SUCCESS]
Ejected ColorModeToggle from @docusaurus/theme-classic to
- ".../src/theme/ColorModeToggle/index.tsx"
- ".../src/theme/ColorModeToggle/styles.module.css"
The generated wrapper imports the original component through the @theme-original alias and types its props with
WrapperProps, and tsc accepts it without changes:
import React, {type ReactNode} from 'react';
import Footer from '@theme-original/Footer';
import type FooterType from '@theme/Footer';
import type {WrapperProps} from '@docusaurus/types';
type Props = WrapperProps<typeof FooterType>;
export default function FooterWrapper(props: Props): ReactNode {
return (
<>
<Footer {...props} />
</>
);
}
Unsafe components
Asking for an eject of DocSidebar (Unsafe for eject) without --danger prints a warning that the action is more likely
to be affected by future breaking changes, and then asks for confirmation with a default of "NO: cancel and stay safe".
Adding --danger skips the question, and swizzle @docusaurus/theme-classic DocSidebar --wrap --typescript --danger
created src/theme/DocSidebar/index.tsx directly. Because the prompt needs a terminal, run swizzle with all the
arguments and flags in scripts.
One npm detail: flags for the command must reach it. With npm run swizzle <theme> <component> -- --wrap --typescript
the double dash passes the flags through, and that worked here with npm 11.19.0 (it created a wrapper for
Footer/Copyright). Without the double dash npm may consume the flags itself (this was seen on npm 11.19.0), so prefer
npx docusaurus swizzle ... when in doubt.
deploy
docusaurus deploy publishes to GitHub Pages in one command: it builds the site (unless --skip-build), clones the
deployment repository, copies the output into it, commits and pushes. Its options are:
| Option | Meaning |
|---|---|
|
Deploys only the given locale. Without it every locale is deployed. |
|
Output folder of the build, default |
|
Deploys the existing output without building, which is useful with a custom pipeline. |
|
Directory in the deployment branch to copy into, default |
|
Path to the configuration file. |
The official deployment guide documents the configuration that the command depends on: organizationName,
projectName and deploymentBranch (it defaults to gh-pages for ordinary repositories and must be set explicitly
when the project name ends in .github.io), plus a recommendation to set trailingSlash explicitly. The
environment variables ORGANIZATION_NAME, PROJECT_NAME and DEPLOYMENT_BRANCH override them. The guide lists these
variables for the command:
| Variable | Meaning |
|---|---|
|
|
|
A GitHub account with push access to the deployment repository. Required without SSH. |
|
A personal access token of that account, for non-interactive use. |
|
The source branch; by default the branch where the command runs. |
|
The |
|
Host and port of a GitHub Enterprise server. |
A typical manual deployment with HTTPS and placeholders for the account looks like this. It was not executed with a real repository, because it pushes to a remote:
GIT_USER=<YOUR_GITHUB_USER> npx docusaurus deploy
Source: Docusaurus docs: Deployment
What was run is the failure outside a Git repository. After a warning that recommends an explicit trailingSlash setting, the command
stops at its first Git call:
[INFO] Deploy command invoked...
[INFO] `git --version` code: 0
[ERROR] Error: Error while executing command `git remote get-url origin`
fatal: not a git repository (or any of the parent directories): .git
Source: Docusaurus docs: Deployment
Most CI setups use the Actions artifact flow instead of this command; both are compared in Deployment.
write-translations
docusaurus write-translations extracts every string that needs translating, from the theme, the configuration (navbar
and footer labels) and the plugins, and writes them as JSON files below i18n/<locale>. The default locale is used
unless --locale is given. Run in the test site, it printed one line per plugin and created these files:
npx docusaurus write-translations
[INFO] 5 translations will be written at "i18n/en/docusaurus-theme-classic/navbar.json".
[INFO] 10 translations will be written at "i18n/en/docusaurus-theme-classic/footer.json".
[INFO] 3 translations will be written at "i18n/en/docusaurus-plugin-content-blog/options.json".
[INFO] 4 translations will be written at "i18n/en/docusaurus-plugin-content-docs/current.json".
Source: Docusaurus docs: CLI
Together with i18n/en/code.json, which holds the strings of the theme and of translate() calls in React code, that
is five files. Every entry has a message and a description, for example the entry named
theme.colorToggle.ariaLabel.mode.system with the message system mode. The options are:
| Option | Meaning |
|---|---|
|
Locale folder to write to; |
|
By default only missing messages are appended to existing files. This option overwrites existing messages. Commit or back up the translations first. |
|
Prefix added to newly written messages, so that untranslated strings stand out in the UI. |
|
Path to the configuration file. |
After adding fr to the locales, write-translations --locale fr with --messagePrefix set to the text TR: and a space created the same five
files in i18n/fr. The theme already ships a French translation for some of its strings. The entry for the light color mode became
TR: mode clair, and a string without a bundled translation became TR: system mode, so the prefix is added to every
message that is written. Without --override, existing messages are kept and only missing ones are appended. The content translations of Markdown files are not produced by this
command; the layout of i18n and the workflow around it are described in
Internationalization.
write-heading-ids
Docusaurus derives an anchor from every heading text. docusaurus write-heading-ids writes explicit ids into the
Markdown source, so that a link such as /docs/pocket-units#converting-a-length survives a rewording of the heading.
Its form is docusaurus write-heading-ids [siteDir] [files...]: the first positional argument is the site directory,
not a file, so a file list must be preceded by it (a dot for the current directory). Without files, all Markdown files
used by the plugins are processed. With a page of the Pocket Units site that has three headings:
npx docusaurus write-heading-ids . docs/pocket-units.md
[SUCCESS] Heading ids added to Markdown files (1/1 files):
- docs/pocket-units.md
Source: Docusaurus docs: CLI
Only second-level headings and below were changed; the page title (the first-level heading) kept no id:
# Pocket Units
## Converting a length {#converting-a-length}
## Converting a temperature {#converting-a-temperature}
The options are --syntax <classic|mdx-comment>, --migrate, --overwrite and --maintain-case:
| Option | Meaning |
|---|---|
|
The default. Writes |
|
Writes |
|
Converts ids that already exist in the other syntax to the target syntax. |
|
Regenerates existing ids from the heading text. |
|
Keeps the casing of the heading instead of lowercasing it. |
The default syntax has a consequence on a site that follows the v4 settings, such as the generated TypeScript site with
future.v4: true: building the page with {#converting-a-length} failed with Could not parse expression with acorn,
because MDX 3 reads braces as JavaScript expressions. Running the command with --syntax mdx-comment --migrate rewrote
the same headings and the build passed again:
npx docusaurus write-heading-ids . docs/pocket-units.md --syntax mdx-comment --migrate
## Converting a length {/* #converting-a-length */}
## Converting a temperature {/* #converting-a-temperature */}
Source: Docusaurus docs: CLI
Adding --overwrite --maintain-case regenerated the ids as Converting-a-length: the case is kept and the words are
joined with hyphens. The official migration guide to v3 keeps {#id} working through markdown.mdx1Compat.headingIds
and shows an escaped form for files that use it; the v4 flag mdx1CompatDisabledByDefault turns those compatibility
options off by default, which is why the comment form is the safer one for new content. Whether to use .md or .mdx for the files is a separate topic, covered in
MDX and React components.
docs:version
docusaurus docs:version <version> freezes a copy of the current docs folder as a numbered version. It takes the
version name as its only argument. Run once in the test site with 1.0.0:
npx docusaurus docs:version 1.0.0
[SUCCESS] [docs]: version 1.0.0 created!
Source: Docusaurus docs: CLI
It created versioned_docs/version-1.0.0, versioned_sidebars/version-1.0.0-sidebars.json and a versions.json file
that contains the single entry 1.0.0. With the two-locale site, it also printed a warning that i18n/fr has no docs
to snapshot, and skipped that locale. Repeating the command with the same name fails:
[ERROR] Error: [docs]: this version already exists! Use a version tag that does not already exist.
Source: Docusaurus docs: CLI
The versions model (current and latest, lastVersion, onlyIncludeVersions, the dropdown, and why to keep few
versions) is the subject of Versioning. For a site that uses several
docs plugin instances the plugin id is part of the command syntax; the CLI reference page does not describe it.
TypeScript support
Docusaurus itself is written in TypeScript, and supports sites whose configuration, sidebars, pages, components and
swizzled theme files are TypeScript. The official guide gives TypeScript 5.1 as the minimum version. The
3.10 release moved new TypeScript sites to TypeScript 6 and to strict mode; the generated site depends on
typescript ~6.0.2 (6.0.3 was installed), @docusaurus/tsconfig, @docusaurus/types and
@docusaurus/module-type-aliases at 3.10.2.
The same release moved the publishing of Docusaurus’s own npm packages to npm Trusted Publishing: CI authenticates to
npm with OIDC instead of a long-lived token, and the packages carry provenance statements that npm audit signatures
can verify. It changes nothing in a site’s configuration.
Source: Docusaurus 3.10 release post
Creating and migrating a site
A new site is created in TypeScript with the --typescript flag, as shown in
Installation and project structure. An existing
JavaScript site is migrated by installing the four packages as development dependencies and adding a tsconfig.json:
npm install --save-dev typescript @docusaurus/module-type-aliases @docusaurus/tsconfig @docusaurus/types
{
"extends": "@docusaurus/tsconfig",
"compilerOptions": {
"baseUrl": ".",
"ignoreDeprecations": "6.0",
"strict": true
},
"exclude": [".docusaurus", "build"]
}
This is the file of the generated 3.10.2 site; the official guide shows the shorter form with only extends and
baseUrl. The base configuration @docusaurus/tsconfig sets, among others, jsx to preserve, target and lib to
ES2022 (with DOM), moduleResolution to bundler, noEmit, skipLibCheck, and a path alias @site/* for files at
the site root. With TypeScript 6 the baseUrl option is deprecated, and removing ignoreDeprecations from the
generated file produced this error from tsc:
tsconfig.json(7,5): error TS5101: Option 'baseUrl' is deprecated and will stop functioning in TypeScript 7.0.
Specify compilerOption '"ignoreDeprecations": "6.0"' to silence this error.
The type-check is separate from the build
The guide states that tsconfig.json is for editor support only: Docusaurus does not use it to compile the project.
The configuration files are loaded on the fly (the jiti loader is used for them in the installed version), so
types are stripped, not checked, and type errors do not stop docusaurus build. The generated package.json adds a
script for the check, "typecheck": "tsc", and the guide suggests running tsc locally or in CI. To see the
difference, the property tagline was misspelled as tagLine in the configuration:
npx tsc
docusaurus.config.ts(9,3): error TS2561: Object literal may only specify known properties, but 'tagLine' does not exist in type 'Config'. Did you mean to write 'tagline'?
tsc exited with status 2 on that file. The build did not use the type information, but it did fail, because the
configuration validator rejects unknown fields (the message shown under "When a build fails" above). With the
typo reverted both commands passed. A reasonable CI order is tsc first, so that the error points at the line, then
docusaurus build.
Typing the configuration
The configuration is typed with Config from @docusaurus/types, and preset and theme options are checked with
satisfies against the types of the preset. This is the shape of the generated docusaurus.config.ts, reduced to a
Pocket Units site:
import {themes as prismThemes} from 'prism-react-renderer';
import type {Config} from '@docusaurus/types';
import type * as Preset from '@docusaurus/preset-classic';
const config: Config = {
title: 'Pocket Units',
tagline: 'Unit conversions for C# and Python',
favicon: 'img/favicon.ico',
future: {
v4: true,
},
url: 'https://your-docusaurus-site.example.com',
baseUrl: '/',
onBrokenLinks: 'throw',
i18n: {
defaultLocale: 'en',
locales: ['en'],
},
presets: [
[
'classic',
{
docs: {
sidebarPath: './sidebars.ts',
},
theme: {
customCss: './src/css/custom.css',
},
} satisfies Preset.Options,
],
],
themeConfig: {
prism: {
theme: prismThemes.github,
darkTheme: prismThemes.dracula,
},
} satisfies Preset.ThemeConfig,
};
export default config;
The sidebars file is typed the same way, with SidebarsConfig from @docusaurus/plugin-content-docs:
import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';
const sidebars: SidebarsConfig = {
tutorialSidebar: [{type: 'autogenerated', dirName: '.'}],
};
export default sidebars;
Both files were part of the generated site and were type-checked and built with it. A site that stays in JavaScript can
get the same checking by adding // @ts-check at the top of docusaurus.config.js and annotating the object with a
JSDoc @type of import('@docusaurus/types').Config; the guide shows this form, and it was not executed here.
TypeScript in the theme and in pages
Files under src (pages, components, swizzled theme components) are compiled by the bundler, which strips the types
as well. All the official themes (theme-classic, theme-live-codeblock and theme-search-algolia) ship TypeScript
variants, which is what swizzle --typescript copies, as shown above. The @docusaurus/module-type-aliases package
declares the @theme/*, @theme-original/*, @docusaurus/* and @site/* module names, so imports in the editor
resolve. A theme author who wants TypeScript support for a theme adds a getTypeScriptThemePath entry to the theme
plugin, as the lifecycle documentation of Docusaurus describes.
Pitfalls and version notes
-
--helpsays the default configuration file isdocusaurus.config.js, yet adocusaurus.config.tsis found first. -
swizzle --listprompts for a language unless--typescriptor--javascriptis given, which hangs a non-interactive script. The same applies to swizzling an unsafe component without--danger. -
write-heading-idstakes the site directory as its first positional argument, so a file list must be preceded by.or the site path. -
The classic
{#id}heading ids fail a build in a site that follows the v4 settings; use--syntax mdx-commentfor new content or migrate the old ones with--migrate. -
clearonly deletes the default output folder, the generated.docusaurusfolder and the bundler cache. -
docusaurus deploypushes with your credentials. KeepGIT_PASSin a CI secret, never in the repository. -
A type error does not fail
docusaurus build; runtscas a separate step. -
In 3.10 TypeScript 6 deprecates
baseUrl, so generated sites carryignoreDeprecations.