How to Develop and Deploy Micro-Frontends with Single-SPA
By Braincuber Team
Published on April 9, 2026
Micro-frontends are the future of front end web development. Inspired by microservices, which allow you to break up your backend into smaller pieces, micro-frontends allow you to build, test, and deploy pieces of your frontend app independently of each other. Depending on the framework you choose, you can have multiple micro-frontend apps written in React, Angular, Vue, or anything else coexisting peacefully together in the same larger app.
What You'll Learn:
- What micro-frontends are and why they matter for modern web development
- How to create a container app using create-single-spa CLI
- Building multiple micro-frontend apps with React
- Registering and coordinating micro-frontend apps
- Setting up activity functions for conditional rendering
- Styling micro-frontend apps and adding React Router
- Configuring Travis CI for continuous deployment
- Deploying to AWS S3 and Heroku
Overview of Micro-Frontends
Micro-frontends extend the microservices architectural pattern to the frontend. Instead of having one monolithic frontend codebase, you split your application into smaller, independently deployable pieces. Each team can own a specific feature or micro-frontend, using their preferred framework and release schedule.
The demo app we will build consists of four separate applications:
Container App
The main page container that coordinates mounting and unmounting of all micro-frontend apps.
Navbar App
A micro-frontend app that is always present on the page for navigation.
Page 1 App
A micro-frontend app that only shows when the user navigates to the page1 route.
Page 2 App
A micro-frontend app that only shows when the user navigates to the page2 route.
Creating the Container App
To generate the apps for this demo, we will use the create-single-spa CLI tool. This tool scaffolds the project structure for you with all the necessary configurations to get started with Single-SPA.
Prerequisites
Make sure you have Node.js and npm or yarn installed before getting started with Single-SPA.
First, create a directory for your project and generate the root config app using the following commands:
mkdir single-spa-demo
cd single-spa-demo
mkdir single-spa-demo-root-config
cd single-spa-demo-root-config
npx create-single-spa
Follow the CLI prompts:
| Prompt | Answer |
|---|---|
| Project Type | single spa root config |
| Package Manager | yarn or npm |
| Organization Name | Your org name (e.g., myorg) |
Creating the Micro-Frontend Apps
Now that we have the container app, let us create the three micro-frontend apps: navbar, page 1, and page 2.
cd ..
mkdir single-spa-demo-nav
cd single-spa-demo-nav
npx create-single-spa
Follow the prompts and select "single-spa application / parcel", "react", your preferred package manager, your organization name, and "single-spa-demo-nav" as the project name.
Repeat this process for page 1 and page 2 apps, replacing "single-spa-demo-nav" with "single-spa-demo-page-1" and "single-spa-demo-page-2" respectively.
Registering Micro-Frontend Apps
One of the container app's primary responsibilities is to coordinate when each app is "active" or not. The container app handles when each app should be shown or hidden using activity functions that return a boolean value.
Creating Activity Functions
Inside the single-spa-demo-root-config directory, create or modify the activity-functions.js file:
export function prefix(location, ...prefixes) {
return prefixes.some(
prefix => location.href.indexOf(`${location.origin}/${prefix}`) !== -1
);
}
export function nav() {
return true;
}
export function page1(location) {
return prefix(location, 'page1');
}
export function page2(location) {
return prefix(location, 'page2');
}
Registering Apps with Single-SPA
Now register the micro-frontend apps in the root-config.js file:
import { registerApplication, start } from "single-spa";
import * as isActive from "./activity-functions";
registerApplication(
"@thawkin3/single-spa-demo-nav",
() => System.import("@thawkin3/single-spa-demo-nav"),
isActive.nav
);
registerApplication(
"@thawkin3/single-spa-demo-page-1",
() => System.import("@thawkin3/single-spa-demo-page-1"),
isActive.page1
);
registerApplication(
"@thawkin3/single-spa-demo-page-2",
() => System.import("@thawkin3/single-spa-demo-page-2"),
isActive.page2
);
start();
Setting Up the Local Import Map
Update the local import map inside the index.ejs file to specify where each app can be found when running locally:
<% if (isLocal) { %>
<script type="systemjs-importmap">
{
"imports": {
"@thawkin3/root-config": "http://localhost:9000/root-config.js",
"@thawkin3/single-spa-demo-nav": "http://localhost:9001/thawkin3-single-spa-demo-nav.js",
"@thawkin3/single-spa-demo-page-1": "http://localhost:9002/thawkin3-single-spa-demo-page-1.js",
"@thawkin3/single-spa-demo-page-2": "http://localhost:9003/thawkin3-single-spa-demo-page-2.js"
}
}
</script>
<% } %>
Running the App Locally
To run your micro-frontend app locally, open four terminal tabs:
Root Config (Port 9000)
In single-spa-demo-root-config: yarn start
Navbar App (Port 9001)
In single-spa-demo-nav: yarn start --port 9001
Page 1 App (Port 9002)
In single-spa-demo-page-1: yarn start --port 9002
Page 2 App (Port 9003)
In single-spa-demo-page-2: yarn start --port 9003
Navigate to http://localhost:9000 to view your app. You will see the navbar app is always visible, and the page 1 and page 2 apps appear based on the URL route.
Specifying Mount Containers
If you refresh your page repeatedly, you may notice that sometimes apps load out of order. This is because we have not specified where each app should be mounted. Fix this by adding HTML containers in index.ejs:
<div id="nav-container"></div>
<main>
<div id="page-1-container"></div>
<div id="page-2-container"></div>
</main>
Then update root-config.js to provide the domElement when registering apps:
registerApplication(
"@thawkin3/single-spa-demo-nav",
() => System.import("@thawkin3/single-spa-demo-nav"),
isActive.nav,
{ domElement: document.getElementById('nav-container') }
);
registerApplication(
"@thawkin3/single-spa-demo-page-1",
() => System.import("@thawkin3/single-spa-demo-page-1"),
isActive.page1,
{ domElement: document.getElementById('page-1-container') }
);
registerApplication(
"@thawkin3/single-spa-demo-page-2",
() => System.import("@thawkin3/single-spa-demo-page-2"),
isActive.page2,
{ domElement: document.getElementById('page-2-container') }
);
Styling the Apps
Add basic styles in index.ejs for consistent styling across all apps:
<style>
body, html { margin: 0; padding: 0; font-size: 16px; font-family: Arial, Helvetica, sans-serif; height: 100%; }
body { display: flex; flex-direction: column; }
* { box-sizing: border-box; }
</style>
Create a root.component.css file in the navbar app with these styles:
.nav {
display: flex;
flex-direction: row;
padding: 20px;
background: #000;
color: #fff;
}
.link {
margin-right: 20px;
color: #fff;
text-decoration: none;
}
.link:hover,
.link:focus {
color: #1098f7;
}
Adding React Router
Install React Router in the navbar app to enable client-side navigation without page refreshes:
yarn add react-router-dom
# or npm install react-router-dom
Update root.component.js in the navbar app to use React Router Link components:
import React from "react";
import { BrowserRouter, Link } from "react-router-dom";
import "./root.component.css";
export default function Root() {
return (
<BrowserRouter>
<nav className="nav">
<Link to="/page1" className="link">
Page 1
</Link>
<Link to="/page2" className="link">
Page 2
</Link>
</nav>
</BrowserRouter>
);
}
Setting Up AWS S3 for Production
To host your micro-frontends in production, you need to create an S3 bucket to store your build artifacts. S3 stands for Simple Storage Service and provides a reliable place to host your JavaScript bundles.
Create an IAM User
Create an IAM user with programmatic access and S3 permissions only. Store the access key ID and secret access key securely.
Create an S3 Bucket
Create a new S3 bucket (e.g., "my-microfrontend-app"). Make sure it is public and enable CORS.
Enable CORS
Add CORS configuration to allow GET requests from any origin for cross-origin resource sharing.
<CORSConfiguration>
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
</CORSRule>
</CORSConfiguration>
Setting Up Travis CI for Continuous Deployment
Integrate Travis CI with your GitHub repos to automate the build and deployment process. Create a .travis.yml file in each micro-frontend project:
language: node_js
node_js:
- node
script:
- yarn build
- echo "Commit sha - $TRAVIS_COMMIT"
- mkdir -p dist/@thawkin3/root-config/$TRAVIS_COMMIT
- mv dist/*.* dist/@thawkin3/root-config/$TRAVIS_COMMIT/
deploy:
provider: s3
access_key_id: "$AWS_ACCESS_KEY_ID"
secret_access_key: "$AWS_SECRET_ACCESS_KEY"
bucket: "my-microfrontend-app"
region: "us-west-2"
cache-control: "max-age=31536000"
acl: "public_read"
local_dir: dist
skip_cleanup: true
on:
branch: master
Store your AWS credentials as environment variables in the Travis CI web console to keep them secure.
Creating the Production Import Map
The import map tells the container app where to find each JavaScript bundle. Create a production import map JSON file and upload it to your S3 bucket:
{
"imports": {
"react": "https://cdn.jsdelivr.net/npm/react@16.13.0/umd/react.production.min.js",
"react-dom": "https://cdn.jsdelivr.net/npm/react-dom@16.13.0/umd/react-dom.production.min.js",
"single-spa": "https://cdn.jsdelivr.net/npm/single-spa@5.5.1/lib/system/single-spa.min.js",
"@myorg/root-config": "https://my-bucket.s3.amazonaws.com/@myorg/root-config/COMMIT_SHA/root-config.js",
"@myorg/nav": "https://my-bucket.s3.amazonaws.com/@myorg/nav/COMMIT_SHA/nav.js",
"@myorg/page1": "https://my-bucket.s3.amazonaws.com/@myorg/page1/COMMIT_SHA/page1.js",
"@myorg/page2": "https://my-bucket.s3.amazonaws.com/@myorg/page2/COMMIT_SHA/page2.js"
}
}
Deploying to Heroku
Create a production server using Express to serve your container app from Heroku:
const express = require("express");
const path = require("path");
const PORT = process.env.PORT || 5000;
express()
.use(express.static(path.join(__dirname, "dist")))
.get("*", (req, res) => {
res.sendFile("index.html", { root: "dist" });
})
.listen(PORT, () => console.log(`Listening on ${PORT}`));
Deploy to Heroku with these commands:
heroku create my-microfrontend-app
git push heroku master
heroku open
Independent Deployment Benefits
One of the main advantages of micro-frontends is the ability to deploy updates independently. With the CI/CD pipeline in place, when you update one micro-frontend app and merge to master, only that app gets rebuilt and deployed.
The import map allows you to reference specific versions of each micro-frontend. When deploying, update the CI pipeline to automatically download the existing import map from S3, update it with the new bundle URL, and re-upload it.
Independent Deployments
Each team can deploy their micro-frontend without coordinating with other teams or waiting for release windows.
Framework Flexibility
Different teams can use React, Angular, Vue, or any framework that supports SystemJS.
Gradual Modernization
You can rewrite parts of your app to new frameworks one piece at a time without a big-bang migration.
Fault Isolation
If one micro-frontend has issues, it does not necessarily take down the entire application.
Frequently Asked Questions
What is Single-SPA?
Single-SPA is a JavaScript framework that enables you to use multiple JavaScript frameworks (React, Angular, Vue) in a single page application. It handles mounting and unmounting apps based on routes.
What are micro-frontends?
Micro-frontends apply the microservices pattern to frontend development. They allow you to split your frontend into smaller, independently deployable pieces that can be built with different frameworks.
How do you deploy micro-frontends?
Build each micro-frontend and upload the JavaScript bundles to a CDN (like AWS S3). Use an import map to reference the bundle URLs. Set up CI/CD pipelines (like Travis CI) to automate builds and deployments.
Can you mix different frameworks in Single-SPA?
Yes, one of Single-SPA's main features is the ability to combine multiple frameworks in the same application. Each micro-frontend can be written in React, Angular, Vue, or any other framework.
What are the drawbacks of micro-frontends?
The main drawbacks include higher initial setup complexity, potential code duplication across micro-frontends, and the operational overhead of managing multiple repositories and deployment pipelines.
Need Help with Micro-Frontend Architecture?
Our frontend architecture experts can help you design and implement micro-frontends using Single-SPA. Whether you are modernizing a legacy application or building a new scalable frontend architecture, we can guide you through the process.
