Migration
This page covers two kinds of migration: moving an existing micro frontend setup into an MFE Forge project, and upgrading between MFE Forge versions.
Upgrading MFE Forge
From the deprecated 2.x–8.x releases
npm versions 2.0.0 to 8.1.0 of mfe-forge were prototype releases and are deprecated. The supported line restarted at 0.1.0. There is no automated migration; create a new project with the current CLI and move your apps into it as described below. If you install by range, make sure it resolves to 0.x:
npm install -D mfe-forge@^0.1.1Within 0.x
All six packages (mfe-forge and @mfe-forge/core, router, store, design, testing) share one version number. Upgrade them together:
npm install -D mfe-forge@<version>
npm install @mfe-forge/core@<version> @mfe-forge/router@<version> @mfe-forge/store@<version> --workspace=<app>Generated apps pin the runtime packages to the exact CLI version that generated them, so each app's package.json needs updating. Before 1.0, a minor bump (for example 0.1.x to 0.2.0) may contain breaking changes; read the changelog and the release notes on GitHub.
Templates are copied at generation time. Upgrading the CLI does not change existing apps; new template improvements only apply to newly generated apps. Compare a freshly generated app (mfe-forge generate app tmp/probe --dry-run lists the files) with your existing ones to port changes.
Upcoming: Module Federation 2.0
The roadmap plans to replace @originjs/vite-plugin-federation with @module-federation/vite in 0.2.0, with remotes read from configuration instead of patched into vite.config.ts, and a codemod. The config schema already accepts federation.plugin: '@module-federation/vite', but no CLI behaviour depends on that value yet. This section will be updated when that release ships.
Moving an existing Vite + Module Federation setup
Create a project next to your code:
bashnpx mfe-forge@latest init my-platform --skip-install cd my-platformSet
"workspaces": ["apps/*/*", "packages/*"]in the rootpackage.json(why).Choose scopes and move apps into
apps/<scope>/<name>. The shell of each scope must live atapps/<scope>/host:textapps/ └── storefront/ ├── host/ # your existing shell ├── catalog/ # existing remotes └── checkout/Every app directory must contain a
vite.config.ts(not.jsor.mts) to be discovered.Align federation names. MFE Forge expects each remote's federation
nameto be camelCase ofscope/nameplusApp(storefrontCatalogApp), itsfilenameto beremoteEntry.jsserved under/assets/, and an exposed./Appmodule. The host'sremotesentries are generated from these conventions, so a remote that keeps its old name will not match.Use literal ports. Put a literal
port: <n>inserverandpreview. Discovery cannot read ports from environment variables.Keep an inline
remotes: { ... }block in the host's federation call.syncreplaces this block as text; remotes built with variables or spreads are not supported. Move any remotes from other scopes or external origins into code outside that block.Share the same singletons in every app:
react,react-dom,react-router-domandzustand, plus@mfe-forge/coreand@mfe-forge/storeif you use their global event bus or store across MFEs.Synchronise and check:
bashnpm install npx mfe sync --dry-run npx mfe sync npx mfe doctorAdd root scripts for apps you moved.
generateaddsdev:<scope>:<name>scripts automatically, but moved apps need them by hand, for example"dev:storefront:catalog": "npm run dev --workspace=storefront-catalog".mfe-forge devruns these scripts by name.
From webpack Module Federation
MFE Forge templates use Vite. Each app needs:
index.htmlat the app root with<div id="root"></div>and<script type="module" src="/src/main.tsx"></script>instead ofHtmlWebpackPlugin;vite.config.tswith@vitejs/plugin-reactand@originjs/vite-plugin-federationconfigured as in the templates (name,filename: 'remoteEntry.js',exposes,shared);- the async boundary pattern:
main.tsxdoesimport('./bootstrap'); build.target: 'esnext', which the templates set because the federation plugin relies on top-level await.
Webpack-specific features such as __webpack_public_path__ or runtime remotes via window containers have no direct equivalent in the originjs plugin. Generate one app with mfe-forge generate app <scope>/<name> and compare its configuration with yours.
From single-spa
single-spa composes applications through lifecycles (bootstrap, mount, unmount) and import maps. MFE Forge composes React components through Module Federation instead:
- Replace each application's single-spa lifecycle export with a default-exported React component (the exposed
./App). - Replace the root config with a host app whose routes render remotes with
LazyRemotefrom@mfe-forge/core. - Replace custom-event communication with
globalEventBusorcreateEventBusfrom@mfe-forge/core, or keep DOMCustomEvents; the two can coexist.
This only works when every application is React. If you need several frameworks on one page, stay with single-spa or combine it with federation yourself.
Gradual migration
For a large monolith, migrate incrementally:
- Create the host and one remote with MFE Forge and serve the legacy app alongside it (for example behind a route that renders the legacy app in an iframe or through its existing mount function).
- Move one feature at a time into a remote.
- Use
@mfe-forge/core's event bus for messages between new remotes, and a thin adapter for messages to the legacy app. - Remove the legacy route when the last feature has moved.
Configuration
mfeforge.config.js (or .ts) must default-export a plain object. A typical migrated config:
/** @type {import('mfe-forge').MFEConfig} */
export default {
name: 'my-platform',
organization: 'acme',
defaults: { packageManager: 'pnpm' },
federation: { shared: ['react', 'react-dom', 'react-router-dom', 'zustand'] },
dev: { portRange: [4000, 4099] },
}All other fields take their schema defaults. See Configuration.