As modern teams migrate legacy applications to Next.js, a common challenge arises: How do we reuse components from the old app without rewriting them all at once?
Enter Module Federation: a powerful Webpack 5 feature that allows you to load remote components from other applications dynamically. This technique, often called micro frontends, enables incremental migration, shared components, and reduced duplication.
In this guide, we walk through:
- Why Module Federation is effective for migrations
- A practical example: Next.js (new app) + Legacy React (old app)
- Step-by-step setup
- Best practices for maintainability
Why Use Module Federation During Migration?
Imagine you’re migrating from a legacy React app (e.g. Create React App) to Next.js. Rewriting every UI component is expensive, risky, and often unnecessary.
Module Federation allows you to:
- Reuse legacy UI components in the new app
- Avoid duplicating shared logic or design systems
- Split teams across legacy and modern stacks
- Migrate pages or features incrementally
Unlike iframes or SSR proxies, this approach delivers live React components with full interactivity.
Architecture Overview
+---------------------------+ +------------------------------+
| Legacy React App | --> | remoteEntry.js (Exposes UI) |
+---------------------------+ +------------------------------+
↳
consumed by
↳
+---------------------------+ +------------------------------+
| Next.js App | <-- | LegacyBanner (Remote UI) |
+---------------------------+ +------------------------------+
Step-by-Step: Setup Module Federation
Prerequisites
- Both apps use Webpack 5
- Legacy app exposes components
- Next.js app consumes components
1. Legacy App (Remote)
In webpack.config.js
:
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
output: {
publicPath: 'http://localhost:3002/',
},
plugins: [
new ModuleFederationPlugin({
name: 'legacyApp',
filename: 'remoteEntry.js',
exposes: {
'./LegacyBanner': './src/components/LegacyBanner',
},
shared: {
react: { singleton: true, eager: true },
'react-dom': { singleton: true, eager: true },
},
}),
],
};
Ensure remoteEntry.js
is publicly accessible.
2. Next.js App (Host)
Install helper package:
npm install @module-federation/nextjs-mf --save-dev
In next.config.js
:
const { withFederatedSidecar } = require('@module-federation/nextjs-mf');
module.exports = withFederatedSidecar({
name: 'nextApp',
remotes: {
legacyApp: 'legacyApp@http://localhost:3002/remoteEntry.js',
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
},
});
3. Use Remote Component in Next.js
// components/LegacyBanner.tsx
import dynamic from 'next/dynamic';
const LegacyBanner = dynamic(() => import('legacyApp/LegacyBanner'), {
ssr: false,
});
export default function Page() {
return (
<div>
<h1>Modern Page</h1>
<LegacyBanner />
</div>
);
}
Gotchas and Best Practices
Tip | Why It Matters |
---|---|
Use singleton for react and react-dom
|
Prevents hook mismatch errors |
Load remotes dynamically with ssr: false
|
SSR support is experimental |
Wrap remotes in error boundaries | Network failures can break the page |
Expose only what you need | Smaller bundles, less leakage |
Add CORS headers on legacy server | Required for cross-origin loading |
Final Thoughts
Module Federation is one of the most powerful tools for modernizing frontends without a complete rewrite.
It provides:
- Better developer experience
- Lower risk
- Shared code and components
If you're deciding between rewriting everything or never touching the legacy app, Module Federation offers a practical alternative.
Top comments (0)