Using this example, you can learn how to set up a microfrontend using single-spa with Module Federation on Vercel. This example demonstrates how to create a microfrontend architecture that allows you to load multiple applications in a single page, enabling independent deployments and development.
The example is available on GitHub at vercel-labs/microfrontends-single-spa-module-federation.
This example demonstrates real-world microfrontend patterns and best practices:
- π Vercel Microfrontends: Production-ready deployment on Vercel's platform
- π Module Federation: Share code between applications at runtime using Webpack's Module Federation
- ποΈ single-spa Integration: Orchestrate microfrontends with single-spa's lifecycle management
- β‘ Runtime Code Sharing: Dynamically load and share components between applications
- π¦ Federated Components: Expose and consume React components across application boundaries
- π Independent Deployments: Deploy each microfrontend without affecting others
- π― Team Autonomy: Enable teams to work independently while sharing code seamlessly
- π§ Vite & Tailwind: Modern build tools with utility-first CSS framework
This example implements a hybrid architecture combining single-spa for application orchestration with Module Federation for runtime code sharing:
-
Root Application (
apps/root/
)- Acts as the shell application and single-spa orchestrator
- Dynamically imports components from content and navigation remotes
- Manages the overall application layout and routing with single-spa
- Configures federated remotes and manages dependencies
-
Content Application (
apps/content/
)- Exposes content-related components through Module Federation
- Provides federated page components wrapped with single-spa lifecycle
- Maintains its own styling and component logic
- Mounted as a single-spa application in the root
-
Navigation Application (
apps/navigation/
)- Exposes navigation components (header and footer) through Module Federation
- Provides reusable navigation components for the entire application
- Each component (header/footer) is registered as separate single-spa applications
- Manages navigation state and user interactions
-
Shared Packages (
packages/
)- Common TypeScript configurations across all applications
- Shared ESLint rules and formatting standards
- Ensures consistency across all microfrontends
architecture-beta
group root(cloud)[Root Application]
group navigation_group(server)[Navigation MFE] in root
group content_group(server)[Content MFE] in root
service header(internet)[Header] in navigation_group
service footer(internet)[Footer] in navigation_group
service landing(database)[Landing] in content_group
service spa_orchestrator(cloud)[Orchestrator] in root
spa_orchestrator:L -- R:header
spa_orchestrator:R -- L:landing
spa_orchestrator:B -- T:footer
This hybrid approach provides:
- single-spa: Application lifecycle management and routing orchestration
- Module Federation: Efficient runtime code sharing and dependency optimization
- Best of both worlds: Robust application management with optimized code sharing
To run the example, you will need the following prerequisites:
- Node.js installed on your machine.
- pnpm package manager installed.
- Git installed to clone the repository.
It is also recommended to have:
- Familiarity with single-spa and Module Federation concepts.
- Basic understanding of Vercel deployment process.
- A Vercel account to deploy the microfrontend applications.
Follow these steps to set up the example on your local machine. Make sure you have the prerequisites installed before proceeding.
-
Clone the repository:
git clone https://github.com/vercel-labs/microfrontends-single-spa-module-federation.git cd microfrontends-single-spa-module-federation
-
Install dependencies:
pnpm install
-
Start the development environment:
pnpm dev
This command starts all applications simultaneously:
- Root app
- Content app
- Navigation app
-
Access the application:
Open http://localhost:3024 in your browser to see the federated application where components from different microfrontends are composed together using single-spa and Module Federation.
This example uses the following technologies:
- singla-spa: A framework for building microfrontends that allows you to load multiple applications in a single page. Learn more about single-spa in the single-spa documentation.
- Module Federation: A feature of Webpack that allows you to share code between multiple applications at runtime. It enables the microfrontend architecture by allowing applications to dynamically load each other. Learn more about Module Federation in the Module Federation documentation.
- Tailwind CSS: A utility-first CSS framework that is used to style the microfrontend applications. Learn more about Tailwind CSS in the Tailwind CSS documentation.
- Vercel: A platform for deploying and hosting web applications, which provides features for managing microfrontends. Learn more about Vercel in the Vercel documentation.
- pnpm: A fast and efficient package manager that is used to manage dependencies in this example. Learn more about pnpm in the pnpm documentation.
- Turborepo: A high-performance build system that is used to manage the monorepo structure of the example. Learn more about Turborepo in the Turborepo documentation.
- Vite: A fast build tool that is used to build the microfrontend applications. Learn more about Vite in the Vite documentation.
The microfrontends are configured using the microfrontends.json
file. This file defines the applications and their routing. The example uses the following configuration:
{
"$schema": "https://openapi.vercel.sh/microfrontends.json",
"applications": {
"microfrontends-single-spa-module-federation-root": {
"development": {
"fallback": "microfrontends-single-spa-module-federation-root.vercel.app"
}
},
"microfrontends-single-spa-module-federation-content": {
"routing": [
{
"paths": ["/_content/:path*"]
}
]
},
"microfrontends-single-spa-module-federation-navigation": {
"routing": [
{
"paths": ["/_navigation/:path*"]
}
]
}
}
}
This configuration is defined in the root application and only need to be defined in the default application. The microfrontends.json
file specifies the applications and their routing paths. The development.fallback
field is used to specify a fallback URL. See more details on how to manage your microfrontends in the Managing Microfrontends documentation.
The microfrontends.json
in this example defines three applications:
microfrontends-single-spa-module-federation-root
: The root application that serves as the entry point for the microfrontend architecture. This is the default application of the Microfrontends group.microfrontends-single-spa-module-federation-content
: A microfrontend application that handles content-related functionality. It is routed to paths starting with/_content/
. The application will provide the landing page content for our example.microfrontends-single-spa-module-federation-navigation
: A microfrontend application that handles navigation functionality. It is routed to paths starting with/_navigation/
. The application will provide the navigation components.
As we don't specify a specific local Microfrontends proxy port, the local Microfrontends proxy will run on port 3024
by default. You can change this by setting localProxyPort
in the microfrontends.json
file.
To make Microfrontends and Module Federation work, we need to configure Vite in each microfrontend application.
For the navigation
and content
applications providing the Module Federation remotes for the root application, we need to set up the Vite configuration as follows:
import { defineConfig, type Plugin } from 'vite';
import { federation } from '@module-federation/vite';
import tailwindcss from '@tailwindcss/vite';
import { microfrontends } from '@vercel/microfrontends/experimental/vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [
tailwindcss(),
microfrontends({
basePath: '/_navigation',
}) as Plugin,
react(),
federation({
name: 'navigation',
manifest: true,
filename: 'remoteEntry.js',
exposes: {
'./header': './src/header/index.tsx',
'./footer': './src/footer/index.tsx',
},
shared: {
react: {
singleton: true,
},
'react/': {
singleton: true,
},
'react-dom': {
singleton: true,
},
'react-dom/': {
singleton: true,
},
},
}) as Plugin[],
],
build: {
target: 'chrome89',
},
});
The only difference between the navigation
and content
applications is the basePath
in the microfrontends
plugin configuration, the name of the Module Federation remote and the exposed modules. The basePath
is set to /_navigation
for the navigation
application and to /_content
for the content
application.
import { defineConfig, type Plugin } from 'vite';
import { federation } from '@module-federation/vite';
import tailwindcss from '@tailwindcss/vite';
import { microfrontends } from '@vercel/microfrontends/experimental/vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [
tailwindcss(),
microfrontends({
basePath: '/_content',
}) as Plugin,
react(),
federation({
name: 'content',
manifest: true,
filename: 'remoteEntry.js',
exposes: {
'./landing': './src/landing.tsx',
},
shared: {
react: {
singleton: true,
},
'react/': {
singleton: true,
},
'react-dom': {
singleton: true,
},
'react-dom/': {
singleton: true,
},
},
}) as Plugin[],
],
build: {
target: 'chrome89',
},
});
Both configurations use the @module-federation/vite
plugin to set up Module Federation and the @vercel/microfrontends/experimental/vite
plugin to enable microfrontends support. The federation
plugin is used to expose the components of the microfrontend applications, while the microfrontends
plugin is used to configure the base path for the microfrontend routing.
For both the navigation
and content
applications, we expose the components that will be used by the root application. The navigation
application exposes a header and footer component, while the content
application exposes a landing component.
React and React DOM are shared as singletons to ensure that the same instance is used across the microfrontends. This is important for maintaining a consistent state and avoiding issues with multiple instances of React.
The additional specification for a build.target
is set to chrome89
to ensure compatibility with the features used in the microfrontends. In this case we need top-level await support, which is available in Chrome 89 and later.
For the root application, we need to set up the Vite configuration to load the microfrontends and use the exposed components from the navigation
and content
applications:
import { defineConfig, type Plugin } from 'vite';
import { federation } from '@module-federation/vite';
import tailwindcss from '@tailwindcss/vite';
import { microfrontends } from '@vercel/microfrontends/experimental/vite';
import { vercelToolbar } from '@vercel/toolbar/plugins/vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [
tailwindcss(),
microfrontends() as Plugin,
vercelToolbar(),
react(),
federation({
name: 'root',
manifest: true,
remotes: {
navigation: {
type: 'module',
name: 'navigation',
entry: '/_navigation/remoteEntry.js',
},
content: {
type: 'module',
name: 'content',
entry: '/_content/remoteEntry.js',
},
},
shared: {
react: {
singleton: true,
},
'react/': {
singleton: true,
},
'react-dom': {
singleton: true,
},
'react-dom/': {
singleton: true,
},
},
}) as Plugin[],
],
build: {
target: 'chrome89',
},
});
The root application doesn't expose any components, but it loads the navigation
and content
applications as remotes using Module Federation. The remotes
configuration specifies the entry points for the remote applications, which are the remoteEntry.js
files exposed by the navigation
and content
applications.
As we are using Vite for the navigation
and content
applications providing the Module Federation remotes, we use remotes specified with type: 'module'
to load the remote applications. This allows us to use the native ES module loading capabilities of the browser, which is supported by Vite.
The entry paths for the remotes are set to /_navigation/remoteEntry.js
and /_content/remoteEntry.js
, which correspond to the base paths defined in the microfrontends.json
file. As we are using Vercel Microfrontends solution and in this example we are using client-side rendering, we can use relative URLs for the remotes. This allows the root application to dynamically load the components from the navigation
and content
applications at runtime. The routing will be managed by Vercel Microfrontends, which will ensure that the correct application is loaded based on the path.
The root application is the entry point for the microfrontend architecture. It is responsible for loading the navigation
and content
remotes and rendering their components as single-spa applications.
To create a layout for the root application using single-spa, we can use the following HTML structure:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Company Landing Page</title>
<link rel="stylesheet" href="./src/globals.css" />
<script type="module" src="./src/index.ts"></script>
</head>
<body className="flex min-h-screen flex-col">
<div id="single-spa-application:header"></div>
<div id="single-spa-application:content"></div>
<div id="single-spa-application:footer"></div>
</body>
</html>
To register each application with single-spa, we can use the following code in the src/index.ts
file of the root application:
import { mountVercelToolbar } from '@vercel/toolbar/vite';
import { registerApplication, start } from 'single-spa';
import './globals.css';
registerApplication(
'header',
() => import('navigation/header'),
() => true,
);
registerApplication(
'footer',
() => import('navigation/footer'),
() => true,
);
registerApplication(
'content',
() => import('content/landing'),
() => true,
);
start();
mountVercelToolbar();
We register each single-spa application using the registerApplication
function. Each application is registered with a name, a loading function that imports the remote component using dynamic ES module import, and an activity function that determines when the application should be active based on the current route, which will be always true in the case of this example.
In the remote applications, we are using single-spa-react
to create a React component that will be rendered by single-spa. The content
application provides the main page content, while the navigation
application provides the header and footer components.
We need to define the bootstrap
, mount
, and unmount
functions for each component. These functions are used by single-spa to manage the lifecycle of the applications.
import React from 'react';
import ReactDOMClient from 'react-dom/client';
import singleSpaReact from 'single-spa-react';
import './globals.css';
function Landing(): React.JSX.Element {
return <>{/* ... */}</>;
}
export const { bootstrap, mount, unmount } = singleSpaReact({
React,
ReactDOMClient,
rootComponent: Landing,
errorBoundary() {
return <></>;
},
});
For the navigation
application, we define the header and footer components similarly.
// ...
export const { bootstrap, mount, unmount } = singleSpaReact({
React,
ReactDOMClient,
rootComponent: Header,
errorBoundary() {
return <></>;
},
});
// ...
export const { bootstrap, mount, unmount } = singleSpaReact({
React,
ReactDOMClient,
rootComponent: Footer,
errorBoundary() {
return <></>;
},
});
The difference being which rootComponent
is used for each application. The Landing
component in the content
application, and the Header
and Footer
components in the navigation
application.
With everything needed set up, you can now run the microfrontend applications locally. The root application will be available at http://localhost:3024
, and it will load the content
and navigation
applications as microfrontends.
To start a development server for each application, we use a dev
npm script in each application's package.json
file. The dev
script uses Vite to start the development server for the application and also specifies the port to run on when using the Microfrontends proxy.
{
"scripts": {
"build": "vite build",
"dev": "vite --port $(microfrontends port)",
"start": "vite preview --port $(microfrontends port)"
}
}
Run the following command in the root directory of the cloned repository to start the development server and the Microfrontends proxy:
pnpm dev
This command will start all the applications in the monorepo and also starts the Microfrontends proxy on port 3024
.
Deploy each microfrontend independently to experience the full power of distributed development:
Each microfrontend can be deployed independently, enabling:
- Faster deployments: Only the changed microfrontend needs redeployment
- Reduced risk: Deployments are isolated and can't break other parts
- Team autonomy: Teams can deploy on their own schedule
- Rollback flexibility: Roll back individual microfrontends without affecting others
-
Fork the repository: Create your own copy of the repository on GitHub.
-
Set up Vercel projects: Create separate Vercel projects for each microfrontend:
- Fork this repository to your GitHub account
- In Vercel, create a new project for each application (
root
,content
,navigation
) - Connect each project to your forked repository
- Set the root directory for each project (
apps/root
,apps/content
,apps/navigation
)
-
Configure Microfrontends group:
- In the Vercel dashboard, create a new Microfrontends group
- Add all three applications to the group
- Set the root application as the default application
-
Environment configuration:
- Ensure each application has the correct build settings
- Verify that the
microfrontends.json
file is properly configured - Set up any required environment variables
-
Deploy and verify:
- Deploy each application independently
- Test the integration by accessing the root application URL
- Verify that all microfrontends load correctly and communicate properly
Each application includes optimized Vercel configuration:
- Framework detection: Automatic Vite optimization
- Build settings: Turborepo-aware build commands with Module Federation support
- Environment variables: Proper environment isolation for each microfrontend
- Edge functions: Optimal performance at the edge with Vercel's global CDN
Once deployed, your applications will be accessible at URLs similar to:
- Root:
https://your-root-app.vercel.app
- Content:
https://your-content-app.vercel.app
- Navigation:
https://your-navigation-app.vercel.app
The root application will automatically load the content and navigation microfrontends based on the routing configuration in microfrontends.json
.
The example is deployed to Vercel at microfrontends-single-spa-module-federation.vercel.app already for you to check out.
You can develop microfrontends in isolation:
# Work on the root application only
cd apps/root
pnpm dev
# Work on the content application only
cd apps/content
pnpm dev
# Work on the navigation application only
cd apps/navigation
pnpm dev
# Build all applications
pnpm build
# Run linting across all apps
pnpm lint
# Type check all applications
pnpm typecheck
# Run all quality checks
pnpm checks
- Create the component in your remote application
- Expose it through Module Federation in
vite.config.ts
:exposes: { './newComponent': './src/path/to/component.tsx' }
- Add TypeScript declarations in the container app's
global.d.ts
:declare module 'remoteName/newComponent' { const Component: React.ComponentType; export default Component; }
- Import and use in the container application:
import NewComponent from 'remoteName/newComponent';
- Shared TypeScript configuration ensures type safety across all federated modules
- Common ESLint rules maintain code quality standards
- Unified Prettier configuration for consistent formatting
- Turborepo orchestrates builds efficiently with caching
- Module Federation optimizes shared dependencies automatically
- Independent builds enable faster CI/CD pipelines
- Shared component patterns across federated modules
- Consistent design tokens and styling approach with Tailwind CSS
- Federated components maintain design system compliance
- Code splitting at the microfrontend level with Module Federation
- Shared chunk optimization for common dependencies (React, React DOM)
- Runtime loading optimization for federated modules
- Top-level await support for modern browser features
Module Federation loading errors:
# Ensure all applications are running
pnpm dev
TypeScript errors with federated modules:
# Verify TypeScript declarations in global.d.ts
# Ensure module names match the federation configuration
Build failures:
# Run type checking to identify issues
pnpm typecheck
# Verify all federated modules are built correctly
pnpm build
This example combines both single-spa and Module Federation for optimal microfrontend architecture:
Feature | single-spa | Module Federation | Combined Approach |
---|---|---|---|
Component Sharing | URL-based routing | Runtime code sharing | Both runtime sharing and routing |
Bundle Size | Independent bundles | Shared dependencies | Optimized shared dependencies |
Integration | Application-level | Component-level | Multi-level integration |
Lifecycle Management | Built-in lifecycle | Manual lifecycle | single-spa manages MF components |
Use Case | App orchestration | Code sharing | Complete microfrontend solution |
Benefits of the combined approach:
- single-spa handles application lifecycle and routing
- Module Federation enables efficient code sharing and dependency management
- Best of both worlds: robust orchestration with optimized runtime sharing
- Independent development: Teams can work on separate microfrontends without coordination
- Technology flexibility: Each microfrontend can use different versions of dependencies
- Deployment independence: Deploy changes without affecting other teams
- Horizontal scaling: Add new microfrontends without modifying existing ones
- Performance isolation: Poor performance in one microfrontend doesn't affect others
- Code organization: Clear separation of concerns across team boundaries
- Isolated testing: Test each microfrontend independently
- Gradual migrations: Update or rewrite microfrontends incrementally
- Clear ownership: Each team owns their microfrontend completely
The Vercel Toolbar is included in the root application to provide insights and debugging capabilities for the microfrontend applications. The toolbar can be mounted using the mountVercelToolbar
function from the @vercel/toolbar/vite
package.
Learn more about the Vercel Toolbar in the Vercel Toolbar documentation and the Microfrontends features of the Vercel Toolbar in the Vercel Toolbar Microfrontends documentation.
This example demonstrates how to set up a microfrontend architecture using single-spa with Module Federation on Vercel. By following the steps outlined in this guide and the provided repo on GitHub, you can create a scalable and maintainable microfrontend application that allows for independent deployments and development.
π Documentation