This content originally appeared on Level Up Coding - Medium and was authored by JealousDev
Modern web apps are evolving into sprawling ecosystems. Monolithic React codebases buckle under the weight of feature creep, conflicting team priorities, and deployment bottlenecks. Enter micro-frontends -a paradigm that decomposes your UI into independent, team-owned “apps within an app.” But how do you glue them together without chaos?
The answer: Webpack Module Federation + Next.js. Let’s build a scalable architecture that balances autonomy with cohesion.
1. Module Federation 101: The Glue That Holds It All Together
Webpack Module Federation (WMF) lets apps dynamically load code from remote sources at runtime. Think of it as a microservice architecture for your frontend.
Real-World Example:
A travel booking platform splits its UI into:
- search-app (Next.js, Team A)
- booking-app (React + Vite, Team B)
- user-profile-app (Remix, Team C)
Each team deploys independently, but WMF stitches them into a seamless experience.
Code Snippet: Basic WMF Setup
// webpack.config.js (Host App)
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'host_app',
remotes: {
search: 'search_app@https://cdn.your-domain.com/remoteEntry.js',
// include more.
},
shared: ['react', 'react-dom'], // Avoid duplicate dependencies
}),
],
};
Insight: Use shared to avoid loading React twice. Version mismatches? Webpack's singleton: true enforces a single instance.
2. Next.js as the Orchestrator: Supercharge Your Host App
Next.js isn’t just for SSR-it’s a powerhouse for micro-frontend routing and performance.
Real-World Pattern:
- Host App: Next.js handles routing, authentication, and global state.
- Remotes: Feature apps (React, Vue, etc.) lazy-loaded via dynamic imports.
Code Snippet: Dynamic Remote Loading
// pages/index.tsx (Host App)
import dynamic from 'next/dynamic';
const SearchApp = dynamic(
() => import('search_app/SearchModule'),
{ loading: () => <p>Loading search...</p>, ssr: false }
);
const HomePage = () => (
<div>
<Header />
<SearchApp /> {/* Rendered from remote! */}
</div>
);
Insight: Disable SSR for remotes if they’re client-side only. Next.js 13+ with React Server Components? Plan carefully-not all remotes play nice with RSC yet.
3. Deployment: CI/CD Pipelines That Don’t Collide
Strategy 1: Independent Deployments
- Each app has its own CI/CD pipeline.
- Use semantic versioning for shared libraries.
Strategy 2: Coordinated Releases
- Deploy the host app after remotes (e.g., booking-app v2.0 requires host-app v1.5).
- Tools like Azure DevOps or GitHub Actions can automate dependency checks.
Real-World Nightmare (and Fix):
A retail app’s checkout page can break because the host app could cache an old remoteEntry.js.
Solution: Use CDNs with immutable filenames (e.g., remoteEntry-[hash].js).
4. Taming Shared Dependencies
Problem: Multiple React instances = silent crashes, hooks hell.
Solution: Enforce dependency alignment:
// In all webpack.config.js files
shared: {
react: { singleton: true, requiredVersion: '^18.2.0' },
'react-dom': { singleton: true, requiredVersion: '^18.2.0' },
},
Pro Tip: Audit dependencies with npm ls react across all apps.
5. CSS Collisions: The Silent Killer
Problem: A .button class in search-app overrides styles in booking-app.
Solutions:
- Scoped CSS: Use CSS Modules or styled-components.
- Atomic CSS: Adopt Tailwind with strict naming conventions.
- Shadow DOM: Extreme isolation for legacy apps.
Real-World Fix:
A fintech app used CSS-in-JS (Emotion) with unique classname hashes to eliminate collisions.
6. Cross-App State Sync: Beyond React Context
Problem: How does search-app notify booking-app that a date was selected?
Solution 1: Event Bus
// shared/eventBus.js
export const bus = new EventEmitter();
// In search-app
bus.emit('dateSelected', payload);
// In booking-app
bus.on('dateSelected', handleDate);
Solution 2: Zustand (Global Store)
// shared/store.js
import { create } from 'zustand';
export useSharedStore = create((set) => ({
selectedDate: null,
setDate: (date) => set({ selectedDate: date }),
}));
Insight: Avoid over-sharing. Not every state needs to be global!
7. Testing: Don’t Let the House of Cards Collapse
- Contract Testing: Ensure remotes comply with host API expectations (use Pact).
- Integration Tests: Run Cypress against a stitched production build.
Pro Tip: Mock remotes in the host’s Jest setup for faster unit tests.
Conclusion: Start Small, Scale with Confidence
Micro-frontends aren’t a silver bullet-they’re a tradeoff. Use them when:
- Teams outgrow monolithic workflows.
- Tech diversity is unavoidable (React + Angular + Vue).
- You need incremental upgrades (migrate legacy code piecemeal).
Final Wisdom: “Micro-frontends solve organizational problems first, technical ones second.”
Ready to Dive Deeper?
Got war stories or questions? Share them below!
Taming the Beast: Mastering Micro-Frontends with Webpack Module Federation & Next.js was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Level Up Coding - Medium and was authored by JealousDev
JealousDev | Sciencx (2025-01-28T18:21:08+00:00) Taming the Beast: Mastering Micro-Frontends with Webpack Module Federation & Next.js. Retrieved from https://www.scien.cx/2025/01/28/taming-the-beast-mastering-micro-frontends-with-webpack-module-federation-next-js/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.