Rsbuild + React Module Federation Monorepo
This is the canonical end-to-end setup for a pnpm monorepo with an Rsbuild
host, header and hero React remotes, Module Federation, and Zephyr Cloud.
It covers a new scaffold and an existing application, then follows the same
configuration, deployment, diagnosis, watch, and browser-verification path for
both.
Every code block on this page is copied from the canonical
zephyr-examples application.
Documentation pull requests compare those blocks with a pinned examples commit.
The examples repository owns the Node 24 and pnpm 10.33.0 executable checks that
build all three applications, reproduce the DTS failure path, and render the
host in Chromium.
The example pins Rsbuild 2.1.8, @module-federation/rsbuild-plugin 2.8.0,
zephyr-rsbuild-plugin 1.2.0, zephyr-agent 1.2.0, TypeScript 6.0.3,
React 19.2.7, and React DOM 19.2.7. Keep declared, locked, and installed
versions aligned before changing configuration.
Choose the correct starting path
New project: scaffold first
Use the official create-rsbuild CLI. Do not synthesize an Rsbuild project
from remembered defaults. The example executes this script in a temporary
directory on every pull request:
Move the three generated applications under apps/, add apps/* to
pnpm-workspace.yaml, and then apply the configuration below.
Existing project: install in place
Do not run a scaffolder over an existing application. Inspect its current entry, TypeScript config, output path, and package manager first. The verified installation script uses exact versions so the package manifest, lockfile, and installed tree cannot silently select different major or minor releases:
Commit the generated pnpm-lock.yaml. In CI and when reproducing a teammate's
failure, install with pnpm install --frozen-lockfile.
Configure the remotes
The header remote establishes the complete contract:
source.entryis explicit and points inside the application's source root.- Module Federation emits
remoteEntry.jsand advertises./Header. - React and React DOM are singleton shared dependencies with pinned required versions.
output.assetPrefixis"auto"so runtime chunks resolve from the remote's own deployment URL.withZephyr()runs after Module Federation so it can inspect and rewrite the federation output.
The ZEPHYR_EXAMPLE_OFFLINE branch is only the example's non-publishing CI
mode. It is never set by the normal build or the live deployment workflow.
In an application without an equivalent isolated fixture, use withZephyr()
unconditionally.
Keep the TypeScript root explicit and keep every exposed file and its imports inside it:
The pinned Header makes browser verification unambiguous:
The hero remote uses its own name, port, expose, and entry while preserving
the same plugin ordering and shared dependency contract:
Configure the host
The host's Module Federation aliases and zephyr:dependencies keys must match
exactly. Here, both contracts use header and hero. A spelling or casing
difference creates two unrelated identities and prevents Zephyr from replacing
the local remote URL.
Use name@URL string remotes with the currently released Zephyr 1.2.x
plugins. Zephyr identifies the alias and replaces the local URL during a
publishing build. Object-form external remotes currently serialize into an
invalid runtime expression when the released plugin rewrites them.
The host entry must be asynchronous. Keep index.ts free of static React and
remote imports and load a separate bootstrap:
Choose remote dependency selectors deliberately
These three forms cover the common monorepo and production cases:
rsbuild-mf-monorepo-header@workspace:*maps the localheaderalias to a unique application UID and selects the most recent remote built with the same branch, target, CI state, and user context.rsbuild-mf-monorepo-header@productionis the short UID form for an application in the same project and organization.productionresolves as an environment first, then a tag, then a version.rsbuild-mf-monorepo-header.design-system.acme@productionis the fullapplication.project.organizationUID and is required across projects or organizations.
See Remote Dependencies for the complete selector grammar.
Selection and emitted URL identity are separate decisions. The default
dependencyUrlMode: "selector" keeps a mutable environment or tag URL in the
host. Moving that environment can retarget an already-built host.
dependencyUrlMode: "version" still uses workspace:* or @production to
choose the deployment at build time, but embeds the selected version's
immutable root, remote entry, and manifest URLs:
Use selector URLs when an environment should retarget consumers without a host rebuild. Use version URLs when a host artifact must always load the exact remote deployment selected during its build.
Build remotes first and the host last
The example's root package.json makes pnpm build run header, then hero,
then host. This remote-first order is required for a cold Zephyr workspace:
the host cannot resolve workspace:* until matching remote deployments exist.
If a remote consumes another remote, topologically order the graph from leaves
to the host and remove circular dependencies before relying on automation.
For local, non-publishing CI, the example uses pnpm build:offline. The
protected live workflow leaves the offline flag unset and uses the same
remote-first pnpm build command.
Before changing configuration in response to a failure, prove package state:
- Declared: inspect each application with
pnpm --filter './apps/header' exec node -p "require('./package.json').devDependencies". - Locked: inspect
pnpm-lock.yamlor runpnpm install --frozen-lockfile. - Installed: run
pnpm --filter './apps/header' list --depth 0andpnpm why @module-federation/rsbuild-plugin.
The example's verification script compares all three states for every pinned Rsbuild, Module Federation, Zephyr, React, and TypeScript package.
Diagnose from evidence
Start with generated configuration, not speculative edits:
- Run
pnpm --filter './apps/header' exec rsbuild inspect. Inspectapps/header/dist/.rsbuild/rsbuild.config.mjsandrspack.config.web.mjsfor the final entry, expose, plugin order, and output path. - Re-run the failing application with
FEDERATION_DEBUG=true pnpm --filter './apps/header' build. - If Module Federation reports
TYPE-001, copy and run the exactargs.cmdcommand printed in that error. It invokes the pinned TypeScript compiler against Module Federation's generated tsconfig and exposes the underlying diagnostic. - In development mode, inspect
.mf/typesGenerate.logfor DTS worker, broker, type download, and hot-reload evidence.
The committed failure case intentionally imports a file outside
compilerOptions.rootDir. With the pinned stack, the asset build can finish
after logging TYPE-001; its generated command exits nonzero with TS6059.
Treat the TYPE-001 event as a DTS failure even when the overall Rsbuild exit
code is zero. .mf/typesGenerate.log is a development-worker log and may not
contain the TypeScript compiler diagnostic itself—the generated command is the
source of truth.
Do not disable DTS, change child-process behavior, pin a different TypeScript
version, or repeatedly move rootDir until that reproduction command shows
which file is outside the compilation boundary.
Troubleshooting matrix
Use the correct watch workflow
Web and TAP watch modes solve different problems:
- For a web production-output loop, run
pnpm --filter './apps/header' exec rsbuild build --watch. For local browser HMR, usepnpm --filter './apps/header' dev. - For a TAP mini-app whose build already emits a publication sidecar, run
pnpm exec ze-cli watch ./dist --target tap-app --metadata ./dist/zephyr-publication.jsonfrom that application's directory.
ze-cli watch is reserved for tap-app; it does not replace Rsbuild's web
watch mode. The example pins zephyr-cli 1.2.0 and CI checks that watch,
--target, tap-app, and --metadata remain valid CLI names and flags.
Verify in a browser
Start header, hero, and host, then verify this chain:
http://localhost:3001/mf-manifest.jsonandhttp://localhost:3002/mf-manifest.jsonreturn 200.- Both
remoteEntry.jsURLs return JavaScript, and each manifest advertises its expected./Headeror./Heroexpose. http://localhost:3000rendersHeader fixture v1andHero fixture.- Browser console and failed network-request lists are empty.
- Change the Header text to
Header fixture v2, wait for the remote rebuild, reload the host, and confirm the new remote renders without rebuilding the host.
The committed Playwright test performs all five checks and restores the Header source after the update test.
Verification and maintenance
The guide is protected at three levels:
- Snippet verification requires every code block to exactly match the pinned canonical example.
- Example pull-request verification
runs the official scaffolder, verified
pnpm addcommands, frozen install, typecheck, remote-first build,rsbuild inspect, CLI flag checks,TYPE-001/TS6059reproduction, and Chromium test. - The protected weekly live workflow
requires the
rsbuild-mf-live-fixtureGitHub environment and Zephyr credential, then publishes both remotes before the host.
With zephyr-examples checked out beside this repository, run
RSBUILD_MF_EXAMPLES_ROOT=../zephyr-examples pnpm verify:rsbuild-mf-guide from
the documentation root to check snippet drift. Run pnpm verify and
pnpm verify:browser inside
zephyr-examples/module-federation/react-rsbuild-monorepo to execute the local
build, diagnostic, and browser contracts.