Docusaurus Faster and v4 Readiness

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) and checked on 2026-10-26. Docusaurus changes its defaults only at major versions, so it ships the next major’s behaviour early, behind switches under the future key of docusaurus.config.ts. Two families of switches matter: future.faster swaps the build tools for faster ones, and future.v4 opts in to the defaults of Docusaurus v4. The examples use the documentation site of Pocket Units, the small units-conversion library with a C# and a Python implementation, in a scratch site generated by create-docusaurus@3.10.2 (classic preset, TypeScript, React 19). Every configuration below was loaded or built with Docusaurus 3.10.2 on Node.js 24.

Why two sets of flags

A Docusaurus site is built in three steps: the MDX files are compiled to React, a bundler produces the browser and Node.js bundles, and the server-side rendering step writes one HTML file per route. Historically each step used a JavaScript tool (Babel, webpack, Terser, cssnano). Replacing them with Rust tools makes the build faster but can change the output slightly, so it could not be forced on existing sites in a minor release. The project therefore offers the new tools as opt-in flags, and promises to make them the default in v4.

Docusaurus documents a naming rule for the future object. Flags with an experimental_ or unstable_ prefix may change in a minor release. Flags namespaced by v4 are expected to become the default in the next major version, and are less likely to change. As of 3.10.2 the object has three relevant members:

  • faster: a boolean or an object of build-tool flags. It needs the @docusaurus/faster package.

  • v4: a boolean or an object of upcoming defaults.

  • experimental_router and experimental_vcs: experimental members, outside the scope of this page apart from the version control strategy that one Faster flag selects.

The key future.faster was called future.experimental_faster until 3.9, and was marked stable in 3.10. The old name now stops the startup with a message. Running the configuration loader on a file that still uses the old names printed:

Error: The Docusaurus config `future.experimental_faster` has been renamed to `future.faster`. Please update your Docusaurus config.
Error: The Docusaurus config `future.experimental_storage` has been promoted to a stable top-level `storage` config attribute. Please move your storage config to the top level.

Docusaurus Faster

Installation

Docusaurus Faster is a separate package so that sites that do not use it do not download Rspack and SWC. It must have the same version as the other Docusaurus packages. The site generated by create-docusaurus@3.10.2 already lists it. The scratch site’s package.json has it next to the core and the preset:

{
  "dependencies": {
    "@docusaurus/core": "3.10.2",
    "@docusaurus/faster": "3.10.2",
    "@docusaurus/preset-classic": "3.10.2"
  }
}

For a site that does not have it yet, the install line pins the version:

npm i -E @docusaurus/faster@3.10.2

The package depends on the Rust tools that do the work: @rspack/core, @swc/core, @swc/html, swc-loader, lightningcss and browserslist. Enabling a flag without the package fails the build. With @docusaurus/faster temporarily removed from node_modules and future: {v4: true} in the configuration, docusaurus build stopped with:

[ERROR] Error: Unable to build website for locale en.
  [cause]: Error: To enable Docusaurus Faster options, your site must add the @docusaurus/faster package as a dependency.

The nine flags

Each flag replaces one tool or one step. They can be set one by one, or all at once with future: {faster: true}.

Flag What it does Replaces Since

swcJsLoader

Transpiles JavaScript and TypeScript with SWC.

Babel

3.6

swcJsMinimizer

Minifies JavaScript with SWC.

Terser

3.6

swcHtmlMinimizer

Minifies HTML and the inlined JavaScript and CSS with SWC.

html-minifier-terser

3.6

lightningCssMinimizer

Minifies CSS with Lightning CSS.

cssnano and clean-css

3.6

rspackBundler

Bundles the app with Rspack.

webpack

3.6

rspackPersistentCache

Keeps a persistent Rspack cache so that later builds are faster. Needs rspackBundler and a preserved node_modules/.cache.

nothing (new)

3.8

mdxCrossCompilerCache

Compiles each MDX file once instead of once for the browser and once for Node.js.

double compilation

3.6

ssgWorkerThreads

Runs static site generation in a pool of Node.js worker threads. Needs future.v4.removeLegacyPostBuildHeadAttribute.

single-threaded rendering

3.8

gitEagerVcs

Selects the default-v2 version control strategy, which reads the whole Git history in one git log call instead of one call per file.

per-file git log

3.10

The first column of versions comes from the release posts: 3.6 introduced the key (then called experimental_faster) with the first five flags and the MDX cache, 3.8 added the persistent cache and the worker threads, and 3.10 added gitEagerVcs and made the key stable.

Dependencies between flags

Two flags refuse to start without another one. The loader reports each case with a message that names both flags, so the error is easy to read:

  • rspackPersistentCache requires rspackBundler.

  • ssgWorkerThreads requires future.v4.removeLegacyPostBuildHeadAttribute.

Running the loader on small files gave these messages:

Error: Docusaurus config flag `future.faster.rspackPersistentCache` requires the flag `future.faster.rspackBundler` to be turned on.
Error: Docusaurus config `future.faster.ssgWorkerThreads` requires the future flag `future.v4.removeLegacyPostBuildHeadAttribute` to be turned on.
If you use Docusaurus Faster, we recommend that you also activate Docusaurus v4 future flags: `{future: {v4: true}}`

The second message points to the way out that the project recommends: if you use Faster, turn on the v4 flags too. The removeLegacyPostBuildHeadAttribute flag removes the old postBuild({head}) plugin API, which prevents the worker threads optimisation, so a community plugin that still uses it must be updated first.

What changes in the output

The documentation says that the Faster tools were adopted conservatively, so the generated site should look the same. Two differences are stated in the 3.6 release post: the HTML minifier now warns about invalid HTML markup, and the static HTML output is about 5% smaller. Faster also consumes less memory.

The published build-time figures

The Docusaurus team published a benchmark in the 3.8 release post, measured on two large sites (ReactNative.dev, about 2000 pages, and the Docusaurus site itself). The figure redraws those numbers; the cold build is the first build with no cache, and the warm rebuild is the next build with the caches in place.

Bar chart of cold build and warm rebuild times in seconds, with and without Docusaurus Faster, for ReactNative.dev and Docusaurus.io

The same posts give per-flag measurements: the 3.6 post reports a production build that is 2 to 4 times faster across three community sites, and the 3.8 post reports about 2 to 5 times faster bundling on rebuilds with the persistent cache, and about twice faster static site generation with the worker threads, measured on a MacBook Pro M3. These are the project’s own figures on large sites; the gain on a small site is small in absolute terms.

I measured the Pocket Units starter site (about 13 pages) on one machine, with the caches cleared before the first build. The numbers are only an illustration of the direction, not a benchmark.

Configuration Cold build Warm rebuild

no future key (webpack, Babel, Terser)

7.8 s

1.7 s

future: {v4: true} (all Faster flags)

2.7 s

1.6 s

On this tiny site the cold build was about three times faster, in line with the published range, and the warm rebuild did not change measurably. The webpack build prints [webpackbar] Compiling Client lines, and the Rspack build does not.

Preserving the persistent cache

rspackPersistentCache writes to node_modules/.cache/rspack. The cache helps only if that folder survives between builds. Hosts such as Netlify and Vercel keep it automatically according to the 3.8 post. In other CI systems, such as GitHub Actions, you have to restore the folder yourself, for example with a cache step keyed on the lock file. Without it, every CI build is a cold build and the flag costs nothing but also gains nothing.

The v4 future flags

The five flags

future.v4 gathers the changes that Docusaurus plans to apply by default in v4. Each one is a breaking change for some sites, which is why none is on by default in 3.x.

Flag Effect

removeLegacyPostBuildHeadAttribute

Removes the legacy postBuild({head}) plugin API so that the build can apply static-site-generation optimisations. Required by ssgWorkerThreads.

useCssCascadeLayers

Enables the CSS cascade layers plugin, which wraps the built-in CSS in layers so that your own unlayered CSS always wins over it.

siteStorageNamespacing

Makes storage.namespace default to true instead of false, so that browser storage keys get a site-specific suffix.

fasterByDefault

Makes every future.faster flag default to true. Needs @docusaurus/faster. Flags that you set explicitly keep your value.

mdx1CompatDisabledByDefault

Makes every markdown.mdx1Compat flag default to false. Flags that you set explicitly keep your value.

The first two flags came with 3.8, where the project introduced the v4 flags and the shorthand future: {v4: true}. The last three are listed as v4 flags in 3.10.

What the v4 shorthand really does

The shorthand is easy to underestimate. Because it includes fasterByDefault, it also turns on all nine Faster flags, which means a site generated by create-docusaurus 3.10 (it contains future: {v4: true}) already builds with Rspack. A site that only wants the v4 defaults, and not Rspack, must opt out explicitly.

I loaded three configurations with the Docusaurus config loader and printed the normalised future.faster, markdown.mdx1Compat and storage values:

import {loadSiteConfig} from '@docusaurus/core/lib/server/config.js';

const {siteConfig} = await loadSiteConfig({
  siteDir: process.cwd(),
  customConfigFilePath: process.argv[2],
});

console.log(JSON.stringify({
  faster: siteConfig.future.faster,
  mdx1Compat: siteConfig.markdown.mdx1Compat,
  storage: siteConfig.storage,
}, null, 2));

The loader is an internal module, so the script is a diagnostic and not a supported API. Its results were:

Setting No future key future: {v4: true} future: {faster: true}

the nine faster flags

all false

all true

configuration rejected

mdx1Compat (comments, admonitions, headingIds)

all true

all false

not loaded

storage.namespace

false

true

not loaded

The third column stops with the error shown earlier, because faster: true includes ssgWorkerThreads, which needs removeLegacyPostBuildHeadAttribute. So faster: true alone is not a valid configuration in 3.10.2; either add the v4 flag or name only the flags you want.

Faster is opt-in in the sense that the faster key is empty unless something sets it, but a site that sets v4: true, as the generated site does, has already opted in to all of it.

The shorthand future v4 true expanding into five flags and the default that each one changes

Opting out of one default

Explicit values win over the defaults that fasterByDefault and mdx1CompatDisabledByDefault set. The following configuration keeps webpack and the MDX v1 admonition titles while taking every other v4 default. It is a complete docusaurus.config.ts for the Pocket Units site.

docusaurus.config.ts
import type {Config} from '@docusaurus/types';
import type * as Preset from '@docusaurus/preset-classic';

const config: Config = {
  title: 'Pocket Units',
  tagline: 'A tiny units-conversion library for C# and Python',
  url: 'https://example.com',
  baseUrl: '/',
  onBrokenLinks: 'throw',

  future: {
    v4: true,
    faster: {
      rspackBundler: false,
      rspackPersistentCache: false,
    },
  },

  markdown: {
    mdx1Compat: {
      admonitions: true,
    },
  },

  presets: [
    [
      'classic',
      {
        docs: {sidebarPath: './sidebars.ts'},
        blog: false,
      } satisfies Preset.Options,
    ],
  ],
};

export default config;

Loading it printed the flags below. rspackPersistentCache: false is part of the file on purpose: the persistent cache needs Rspack, and under v4: true it defaults to true, so leaving it out gives the same dependency error as before.

"faster": { "rspackBundler": false, "rspackPersistentCache": false, "swcJsLoader": true,
            "swcJsMinimizer": true, "swcHtmlMinimizer": true, "lightningCssMinimizer": true,
            "mdxCrossCompilerCache": true, "ssgWorkerThreads": true, "gitEagerVcs": true },
"mdx1Compat": { "admonitions": true, "comments": false, "headingIds": false },
"storage": { "type": "localStorage", "namespace": true }

Storage namespacing

siteStorageNamespacing changes browser storage keys from theme to theme-<hash>. The aim is to stop two sites on the same origin, such as two localhost:3000 apps or /app and /docs under one domain, from overwriting each other’s keys. The consequence for a deployed site is that visitors' saved colour mode and tab choices are reset once. The storage key itself is top-level and stable since 3.10:

const config = {
  storage: {
    type: 'localStorage',
    namespace: true,
  },
};

type is localStorage or sessionStorage, and namespace is a boolean or a string that is appended to the keys.

Cascade layers

With useCssCascadeLayers, the CSS of Infima, the theme and the plugins is placed inside layers named docusaurus.infima, docusaurus.theme-common, docusaurus.theme-classic and so on. I built the starter with and without the flag and searched the generated stylesheet: the layered build contains @layer docusaurus.infima and the other layers, and the default build contains none. CSS that you write in custom.css is not in a layer, so it always beats the built-in rules whatever their specificity or order. The flag can slightly change which rule applies on sites with heavily customised or swizzled styles. See Styling, theming and swizzling for the cascade.

Syntax being phased out

The three mdx1Compat options

Docusaurus compiled files with MDX v1 until v3. MDX v1 was forgiving, and Docusaurus added three conveniences on top of strict MDX v3. Since 3.0 the markdown.mdx1Compat object keeps them working, and all three default to true. In v4 they default to false, and future.v4.mdx1CompatDisabledByDefault previews that.

Option Old syntax (works with the option on) Strict replacement Result with the option off

comments

<!-- a comment --> HTML comment

{/* a comment */} JSX comment

compile error

admonitions

:::note My Title

:::note[My Title]

the lines are shown as plain text

headingIds

## Heading {#my-id}

## Heading {/* #my-id */}

compile error

I built a one-page Pocket Units document for each form, once with the default compatibility and once with future: {v4: true}. The results show that the three options fail in different ways:

Source Compatibility on After v4: true

<!-- a comment -->

builds

fails: Unexpected character before name, with a hint to use a JSX comment

{/* a comment */}

builds

builds

## Heading {#my-id}

builds, id my-id

fails: Could not parse expression with acorn

## Heading {/* #my-id */}

builds, id my-id

builds, id my-id

## Heading \{#my-id}

builds, id my-id

builds, id my-id

:::note My Title with a body and :::

builds as an admonition titled My Title

builds with no error; the lines appear as a plain paragraph

:::note[My Title]

builds as an admonition

builds as an admonition

The last two rows matter most. The old admonition title is not reported when the option is off: it is silently rendered as text, so a page can lose its warning box without any build error. Search the content for admonition openers that have text after the type before turning the flag on.

Comments

MDX v3 has no HTML comments. A blog post can be truncated with {/* truncate */} instead of <!-- truncate -->, and the default truncate marker accepts both. For editor and formatter support, the migration guide also recommends {/* prettier-ignore */} before content that Prettier cannot parse as MDX v3.

Admonition titles

The directive syntax puts the title in square brackets, so Docusaurus can use the generic remark-directive plugin:

:::warning[Rounding]
`Convert(0.1, "m", "ft")` is rounded to six decimals.
:::

The migration to v3 also made :::warning an official yellow admonition (it was an undocumented red one in v2) and deprecated :::caution. Use :::warning[caution] for a yellow box or :::danger for the red one.

Heading IDs

The explicit heading ID {#my-id} is another Docusaurus extension that is not valid MDX. The 3.10 release post introduces a native form that is a JSX comment, {/* #my-id */}, and the migration guide shows the escaped classic form \{#my-id}, which still produces the same ID. The CLI can write and convert IDs.

npx docusaurus write-heading-ids --syntax mdx-comment --migrate

Its options are --syntax (classic or mdx-comment, default classic), --migrate (convert existing IDs to the target syntax), --overwrite (regenerate existing IDs from the heading text) and --maintain-case (keep the casing of the heading text). On a file with one classic ID and one heading without an ID the command rewrote the file to:

## Meters to feet {/* #meters-to-feet */}

### Plain heading {/* #plain-heading */}

The command processes all documents of the site, so the starter documents were changed too; run it on a clean working tree and review the diff. --migrate keeps the text of each existing ID, so incoming links to the anchors keep working.

The v3 migration guide still says that classic IDs should stay enabled until a new syntax is provided. The 3.10 release post is the newer source and introduces the JSX comment form.

Other strictness changes

Some constructs were already errors in v3 and are not controlled by the options. The migration guide lists them with the error messages:

  • A { that does not start a valid JavaScript expression (Could not parse expression with acorn). Use inline code, the entity &#123;, or \{.

  • A < that does not start a valid tag, such as <5 or Array<T> in prose (Unexpected character, Expected a closing tag). Use inline code, < or \<.

  • Autolinks written as http://localhost:3000. Write a regular Markdown link or the bare URL.

  • A colon after a word, such as re:Invent, parsed as a text directive. Escape it as \: or add a space.

  • Indented code blocks, which MDX no longer treats as code. Use fences.

  • Emphasis next to punctuation in languages that do not use spaces, where ** can stop working.

Files that contain none of these can stay .md, but the project recommends .mdx for any file that uses MDX features, because .md files will be parsed as CommonMark in a future version. The option markdown.format accepts mdx (the default), md and detect (by file extension), and CommonMark mode does not yet have full feature parity with MDX.

Recap of the v2 to v3 migration

Sites that are still on v2 meet the v3 changes first, and v4 readiness builds on them. The guide groups them as dependencies, MDX and other breaking changes.

Area Change in Docusaurus v3

Runtime

Node.js 18 or later (3.9 moved the minimum to Node.js 20), React 18 or later, TypeScript 5.1 or later.

MDX

MDX v1 replaced by MDX v3: stricter { and <, no autolinks in angle brackets, no indented code, text directives, extra paragraphs in HTML mixed with Markdown, sandboxed lower-case component mappings.

Packages

@mdx-js/react ^3, prism-react-renderer ^2 (themes imported from the package root, fewer languages included by default), react-live 4, remark-emoji 4, Mermaid 10.4 or later, remark-math 6 and rehype-katex 7.

Configuration

ESM and TypeScript configuration files supported, config loader changed from import-fresh to jiti, @docusaurus/tsconfig replaces the external @tsconfig/docusaurus.

Content

:::warning made official and :::caution deprecated, the blog RSS feed limited to 20 entries (set feedOptions.limit to false for the old behaviour), versioned sidebars lose the version-x/ prefix.

Themes

Docs theme components refactored: delete and re-swizzle any ejected @theme/DocPage and related components.

Tooling

The docusaurus-mdx-checker command lists the files that fail to compile under MDX v3. It was not run here, because the scratch site is already strict.

Math plugins are ESM only, so a CommonJS configuration must use an async function with a dynamic import(). The page on diagrams and math shows the working configuration.

Other deprecations on the way to v4

  • onBrokenMarkdownLinks at the top level is deprecated since 3.9 and removed in v4. Use markdown.hooks.onBrokenMarkdownLinks and markdown.hooks.onBrokenMarkdownImages. The top-level onBrokenLinks stays and accepts ignore, log, warn and throw.

  • future.experimental_storage became the top-level storage in 3.10.

  • The build of 3.10 requires Node.js 20 or later, and TypeScript 6 is the default for new sites (it needs ignoreDeprecations: "6.0" in the generated tsconfig.json).

  • React 19 is supported.

See Configuration for the complete list of configuration keys.

Upgrade checklist

The project’s own advice is to adopt the changes one at a time, building after each step, and to read the release post of each minor version because new future flags appear in minor releases. The order below goes from the cheapest and most reversible step to the strictest one.

flowchart TD A["Update to the latest 3.x release"] --> B["Add @docusaurus/faster at the same version"] B --> C["Enable future.faster flags and build"] C --> D["Enable v4 flags one by one and build"] D --> E["Rewrite comments, admonition titles and heading IDs"] E --> F["Replace future.v4 flags with future.v4: true"] F --> G["Plan the move to v4 when it is released"]
  1. Update every @docusaurus/* package to the same 3.x version and check that community plugins and themes support it.

  2. Run a normal build and fix onBrokenLinks and the deprecation warnings first.

  3. Add @docusaurus/faster at the same version. Enable the flags with future.faster, building after each one if you want to find a regression. Remember that rspackPersistentCache needs rspackBundler and a cached node_modules/.cache.

  4. Turn on removeLegacyPostBuildHeadAttribute, then ssgWorkerThreads. If a community plugin fails, update or replace it.

  5. Turn on useCssCascadeLayers and compare the pages in a browser, with extra care for swizzled components and custom CSS.

  6. Turn on siteStorageNamespacing knowing that visitors' stored preferences reset once.

  7. Migrate the content: HTML comments to JSX comments, admonition titles to square brackets, heading IDs with write-heading-ids --syntax mdx-comment --migrate, and rename files that use MDX features to .mdx.

  8. Turn on mdx1CompatDisabledByDefault last, and search the content for old admonition titles that would become plain text.

  9. Replace the five individual flags with future: {v4: true}, which also pulls in new v4 flags that later 3.x minors may add. Read the release post of each minor before upgrading.

Pitfalls and version notes

  • future: {faster: true} alone is invalid in 3.10.2; it needs the v4 head attribute flag (or ssgWorkerThreads: false).

  • future: {v4: true} enables Rspack. Opting out of rspackBundler also requires rspackPersistentCache: false.

  • future: {v4: true} without @docusaurus/faster in the dependencies fails with the message that asks for the package.

  • The persistent cache does nothing in CI unless node_modules/.cache is restored between runs.

  • With the MDX v1 options off, an old admonition title is not an error: the box disappears.

  • Old names future.experimental_faster and future.experimental_storage stop the startup in 3.10.

  • New future flags can appear in any minor release, so the v4: true shorthand can change behaviour when you upgrade the minor version.

  • This page documents 3.10.2. Docusaurus v4 is available only as canary builds as of the check date, so none of the v4 behaviour above was run on a v4 release.