Single-SPA Cheat Sheet
single-spa orchestrates multiple frontend microfrontends in one browser tab: a root config registers apps, routes activate them, and lifecycles mount/unmount each framework.
How to use this Single-SPA cheat sheet registerApplication(name, loadApp, activeWhen) tells the root config when to fetch and bootstrap each microfrontend. Lifecycles bootstrap, mount, and unmount integrate React, Vue, or Angular apps into shared DOM containers.
Parcels are mountable widgets without routing—useful for embedding one app inside another. CSS isolation relies on scoped styles, shadow DOM, or naming conventions; shared dependencies reduce duplicate React loads via import maps or module federation.
Quick Single-SPA example import { registerApplication, start } from 'single-spa';
registerApplication({
name: '@org/navbar',
app: () => System.import('@org/navbar'),
activeWhen: ['/'],
});
registerApplication({
name: '@org/dashboard',
app: () => System.import('@org/dashboard'),
activeWhen: (location) => location.pathname.startsWith('/dashboard'),
});
start(); Root Config & registerApplication Option Example Notes name "@org/app" Unique string identifier app () => System.import("@org/app") Lazy load microfrontend bundle activeWhen "/settings" or function When app should mount customProps customProps: { authToken } Pass shared data to lifecycles start() start({ urlRerouteOnly: true }) Begin listening to routing Layout engine single-spa-layout routes + slots Declarative DOM placement Import map <script type="systemjs-importmap"> Resolve bare specifiers in browser index.html One root config entry script Host page loads orchestrator only
Application Lifecycles Lifecycle Example Purpose bootstrap export async function bootstrap(props) One-time setup before first mount mount export async function mount(props) Render into DOM container unmount export async function unmount(props) Teardown when route deactivates unload Optional; rarely used After unmount when app may not return timeout timeouts: { mount: { millis: 5000 } } Fail fast on hung mounts single-spa-react singleSpaReact({ React, ReactDOM, rootComponent }) Generates lifecycles for React single-spa-vue singleSpaVue({ Vue, appOptions }) Vue 2/3 adapter Error boundary Catch mount failures in root Show fallback UI per app
Parcels & CSS Isolation Topic Example Notes mountRootParcel mountRootParcel(parcelConfig, props) Embed microfrontend in another app Parcel lifecycles Same bootstrap/mount/unmount No activeWhen routing Unmount parcel parcel.unmount() Clean up when parent unmounts CSS modules styles.button Scoped class names per build BEM prefix .app-dashboard__btn Namespace global CSS Shadow DOM Web components wrapper Strong isolation; tricky with global tokens Shared design tokens CSS variables on :root Coordinate across microfrontends Z-index scale Document layer stack Prevent modal/overlay conflicts
Routing & Shared Dependencies Pattern Example Notes History API single-spa listens to popstate Use pushState in apps consistently Base path activeWhen: "/app1" Each app owns URL prefix Default route Redirect / to primary app Root config or layout handles Shared React Webpack externals + import map One React copy in browser Version mismatch Two Reacts break hooks Align peer dependency versions Module federation Host loads remoteEntry.js Alternative to SystemJS import maps Cross-app comms Custom events or shared store Avoid tight coupling; prefer URL state Local dev Import map overrides in devtools Point to localhost webpack dev server
React microfrontend lifecycles import React from 'react';
import ReactDOMClient from 'react-dom/client';
import singleSpaReact from 'single-spa-react';
import App from './App';
const lifecycles = singleSpaReact({
React,
ReactDOMClient,
rootComponent: App,
errorBoundary(err) => React.createElement('div', null, 'App crashed'),
});
export const { bootstrap, mount, unmount } = lifecycles; Parcel and application configs share the same lifecycle shape.
Layout route with single-spa-layout <single-spa-router>
<application name="@org/nav"></application>
<route path="dashboard">
<application name="@org/dashboard"></application>
</route>
</single-spa-router> Layout engine mounts apps into designated DOM nodes.
Import map for shared dependencies <script type="systemjs-importmap">
{
"imports": {
"react": "https://cdn.example/react@18.3.1/production.min.js",
"react-dom": "https://cdn.example/react-dom@18.3.1/production.min.js",
"@org/navbar": "https://cdn.example/navbar/main.js"
}
}
</script> Pin exact versions; mismatch across apps causes subtle runtime bugs.
Common mistakes Loading two copies of React because externals and import maps were not aligned. Global CSS from one microfrontend leaking styles into another without namespacing. Forgetting unmount cleanup—event listeners and timers survive route changes. Using activeWhen string match when pathname includes locale prefix (/en/app). Key takeaways Root config registers apps with activeWhen; start() enables routing. bootstrap/mount/unmount integrate any framework via adapters. Parcels embed apps without route ownership. Isolate CSS and share one copy of major libraries via import maps or federation.
Pro Tip
Document a z-index and modal portal convention across teams before the first overlay bug ships to production.
Common Single-SPA Mistakes Go to next item