single-spa is a JavaScript framework for front-end microservices. It allows you to build independent, deployable applications (often called “microservices” or “microfrontends”) that can be combined to create a larger, more complex application. Instead of a monolithic application, you have multiple smaller applications working together. This approach offers benefits such as improved code organization, independent deployments, and better team scalability. single-spa handles the routing and lifecycle management of these individual applications, ensuring seamless integration and navigation between them.
To get started with single-spa, you’ll need to install it using npm or yarn. Open your terminal and run the following command:
npm install single-spa
# or
yarn add single-spa
This will install the core single-spa library in your main application. You will also need to install separate packages for each of your microfrontends, though their installation depends on the framework they’re built with (React, Angular, Vue, etc.).
This section guides you through creating a simple single-spa application with a single microfrontend. We will use Parcel for simplicity, but you can adapt to Webpack or other bundlers.
Create a root application: Create a new directory for your root single-spa application (e.g., my-single-spa-app
). Inside this directory, create the following files:
index.html
: This will be the main HTML file for your application.index.js
: This will contain the single-spa configuration.package.json
: Define your project (using npm init -y).index.html
:
<!DOCTYPE html>
<html>
<head>
<title>My Single-SPA App</title>
</head>
<body>
<div id="root"></div>
<script src="/index.js"></script>
</body>
</html>
index.js
:
import { registerApplication, start } from 'single-spa';
registerApplication(
'app1',
=> import('./app1.js'), //Import function for your app
() => location.pathname.startsWith('/app1') // active when route starts with '/app1'
() ;
)
start();
app1.js
(simple React example): Create app1.js
in the same directory. This example assumes you have a basic React project set up already.
export async function bootstrap(props) {
console.log('app1 bootstrapped');
// your application's bootstrap logic, which will be called once before mounting.
}
export async function mount(props) {
const root = document.createElement('div');
root.id = 'root-app1';
document.body.appendChild(root);
//Mount your React app here, eg. ReactDOM.render(<App />, root);
console.log('app1 mounted');
}
export async function unmount(props) {
// your application's unmount logic, called once before the application is unmounted.
console.log('app1 unmounted');
}
Remember to replace the placeholder comment in app1.js
with the actual mounting logic for your React app.
You can use a simple static file server (like parcel
) or a more sophisticated setup (like Webpack Dev Server). For this example, parcel
is used:
npm install -D parcel
parcel index.html
Navigate to http://localhost:<port>
(the port will be displayed in your terminal) in your browser. You should see your app1. Remember to navigate to /app1
in your URL to activate your application.
single-spa relies on several key concepts:
Applications: These are the independent microfrontends. Each application has its own lifecycle (bootstrap, mount, unmount) and is independently developed and deployed.
Parcel/Webpack configuration (or other bundler): This is how you bundle your single-spa root config and how to bundle your individual applications.
registerApplication
: This function registers a new application with single-spa. It takes three arguments: a unique name for the application, a function to load the application, and a function to determine when the application should be active (based on URL or other criteria).
Lifecycle functions (bootstrap
, mount
, unmount
): These functions control the application’s lifecycle within single-spa. bootstrap
performs initial setup, mount
renders the application, and unmount
cleans up after the application.
start()
: This function initiates the single-spa routing and lifecycle management. It begins checking URL changes and activating/deactivating registered applications accordingly.
Understanding these core concepts is fundamental to building robust and scalable single-spa applications. Each concept will be elaborated on in later sections of this manual.
Registering an application with single-spa makes it known to the single-spa router. This is done using the registerApplication
function. This function takes three arguments:
name
(String): A unique name for your application. This name is used internally by single-spa to identify and manage the application.
app
(Function): A function that returns a promise which resolves to the application’s exported lifecycle functions (bootstrap
, mount
, unmount
). This function is typically implemented using dynamic imports (import()
). This allows for lazy loading of your applications.
activeWhen
(Function): A function that returns a boolean indicating whether the application should be active based on the current URL or any other criteria. This function is called frequently by single-spa to determine which application(s) should be mounted.
Example:
import { registerApplication } from 'single-spa';
registerApplication(
'app1',
=> import('./app1.js'),
() => location.pathname === '/app1'
location ; )
In this example:
'app1'
is the application’s name.() => import('./app1.js')
loads the application’s lifecycle functions using dynamic import.location => location.pathname === '/app1'
makes the application active only when the URL pathname is exactly /app1
.Each single-spa application exposes three lifecycle functions:
bootstrap(props)
: This function is called once, before the application is mounted for the first time. It’s used for any one-time setup or initialization required by the application. This function returns a promise.
mount(props)
: This function is called whenever the application becomes active (as determined by activeWhen
). It’s where you actually render your application’s UI into the DOM. This function returns a promise.
unmount(props)
: This function is called whenever the application becomes inactive. It’s used to clean up the application, such as removing its DOM elements and unregistering any event listeners. This function returns a promise.
These functions are passed a props
object containing useful information such as the application’s name
and customProps
(more on this below). Each function should return a promise that resolves once the operation is complete.
single-spa doesn’t handle routing within your microfrontends. It only manages the activation and deactivation of microfrontends based on the URL. Your activeWhen
function dictates which application is active based on the current URL. You can use any logic you want within activeWhen
, including regular expressions for more complex routing.
Example using a regular expression:
registerApplication(
'app2',
=> import('./app2.js'),
() => location.pathname.startsWith('/dashboard')
location ; )
This registers app2
to be active whenever the URL starts with /dashboard
. The internal routing within app2
would then handle navigation within the dashboard section.
By default, single-spa loads applications using dynamic imports. However, you can customize the loading strategy. For instance, you might want to pre-load certain applications to improve perceived performance or have a different mechanism for loading certain applications. This is often achieved by modifying the app
function passed to registerApplication
.
single-spa is framework-agnostic. You can build your microfrontends using any framework (React, Angular, Vue, Svelte, etc.). The key is that each microfrontend must export the bootstrap
, mount
, and unmount
lifecycle functions in a way that single-spa understands. The implementation of these functions will naturally vary depending on the framework you’re using, but the interface to single-spa remains the same.
customProps
: You can pass custom data to your applications via the customProps
option in the registerApplication
function:
registerApplication(
'app3',
=> import('./app3.js'),
() => location.pathname === '/app3',
location
{customProp1: 'value1',
customProp2: 123
}; )
This passes {customProp1: 'value1', customProp2: 123}
to app3
’s lifecycle functions in the props
object.
loadError
Handling: You can handle errors during the loading of your applications using a .catch()
on the promise returned by the app
function provided to registerApplication
.
While bootstrap
, mount
, and unmount
are the standard lifecycle functions, you can extend single-spa’s functionality by creating and using custom lifecycle functions. These are added using the addErrorHandler
, addLifeCycle
functions. This allows you to add extra steps to the application lifecycle without modifying the core single-spa behavior. Refer to the official single-spa documentation for details on how to implement custom lifecycles.
single-spa itself doesn’t handle bundling or code splitting. It relies on your build process (e.g., Webpack, Parcel, Rollup) to create separate bundles for each of your microfrontends. The key is that each microfrontend should be independently buildable and deployable. single-spa’s role is to orchestrate the loading and lifecycle management of these pre-built bundles. It dynamically loads these bundles using techniques like dynamic import()
statements, allowing for efficient lazy-loading.
Optimizing load times is crucial for a positive user experience in a single-spa application. Here are some strategies:
Code Splitting: Break down your applications into smaller, more manageable chunks. Load only the necessary code when needed. This is a fundamental aspect of optimizing a single-spa application.
Lazy Loading: Load applications only when they are needed, based on the route or user interaction. single-spa’s activeWhen
function is central to this.
Caching: Utilize browser caching mechanisms (e.g., service workers, HTTP caching headers) to avoid re-downloading application code if it hasn’t changed.
Minification and Compression: Minify your JavaScript and CSS code to reduce file sizes. Use GZIP or Brotli compression to further reduce transfer sizes.
Pre-fetching/Pre-loading: For frequently accessed applications, consider pre-loading or pre-fetching them in the background to reduce the perceived wait time.
Code splitting is essential for optimizing single-spa applications. Instead of having one large bundle for each microfrontend, split your code into smaller chunks based on functionality or features. This reduces the initial load time and allows the browser to only download the code needed for the currently active application. Modern bundlers like Webpack and Parcel offer robust support for code splitting. The import()
statement is how single-spa facilitates the lazy loading of these code chunks on demand.
Effective caching can significantly improve performance. Leverage browser caching to avoid repeatedly downloading the same application code. This includes:
HTTP Caching Headers: Configure appropriate Cache-Control
headers on your application’s assets to set appropriate caching policies (e.g., max-age).
Service Workers: Implement a service worker to cache application assets offline and improve load times even when the user is offline.
Bundler Caching: Many bundlers provide caching mechanisms that can speed up subsequent builds.
single-spa is compatible with various build tools like Webpack, Parcel, Rollup, and others. The choice of build tool depends on your project’s needs and preferences. Each tool has its own mechanisms for code splitting, optimization, and caching. It’s crucial to configure your chosen build tool correctly to optimize your single-spa application. Ensure your configuration leverages features like code splitting, minification, and appropriate caching strategies.
Performance considerations are crucial for a successful single-spa architecture:
Avoid blocking rendering: Ensure your application loading doesn’t block the rendering of the main page. Properly handle asynchronous loading to avoid the dreaded “white screen of death.”
Optimize application size: Smaller application bundles lead to faster load times. Optimize your code, images, and other assets to reduce their size.
Measure and monitor performance: Regularly measure performance metrics (e.g., load time, time to interactive, bundle size) using tools like Lighthouse or WebPageTest. This helps identify areas for improvement.
Efficient DOM manipulation: In your mount
and unmount
functions, minimize unnecessary DOM manipulation. Use techniques like virtual DOM (if applicable) to improve rendering performance.
Resource management: Ensure your applications release resources (e.g., event listeners) properly in their unmount
function to prevent memory leaks.
By focusing on these optimization strategies, you can build high-performing single-spa applications that provide a smooth user experience.
single-spa’s core routing mechanism is based on the browser’s URL. However, you can integrate single-spa with other routing libraries, such as React Router, Angular Router, or Vue Router, within your individual microfrontends. single-spa itself doesn’t dictate or interfere with the internal routing of your microfrontends. The activeWhen
function in registerApplication
determines which microfrontend is active based on the URL, and the internal routing libraries handle navigation within each microfrontend. You might need to adjust how you handle navigation within each microfrontend to ensure smooth transitions between different parts of your application.
Robust error handling is essential for a production-ready single-spa application. You should implement error handling at multiple levels:
During application loading: Use try-catch blocks around your import()
statements in the app
function passed to registerApplication
to handle errors that may occur while loading a microfrontend. The loadError
function can be used for more advanced handling.
Within microfrontend lifecycles: Handle errors within the bootstrap
, mount
, and unmount
functions of your microfrontends using appropriate error handling mechanisms specific to your chosen framework.
Global error handling: Consider using a global error handler (e.g., window.onerror
) to catch any unhandled exceptions that might occur within your single-spa application.
single-spa is designed to work well with various libraries and tools. You can integrate it with:
State management libraries: Redux, Zustand, Vuex, or NgRx can be used within your individual microfrontends to manage their state.
UI component libraries: React Bootstrap, Material-UI, Ant Design, etc., can be integrated into your microfrontends.
Testing frameworks: Jest, Cypress, Mocha, etc., are compatible for testing your microfrontends and the overall single-spa application.
Analytics and monitoring tools: Integrate tools like Google Analytics or other monitoring services to track application usage and performance.
The key is to ensure that these integrations are done within the individual microfrontends, keeping them independent and loosely coupled.
Testing a single-spa application requires a multi-faceted approach:
Unit tests: Test individual components and functions within each microfrontend using appropriate testing frameworks (Jest, Mocha, etc.).
Integration tests: Test the interaction between different microfrontends. This often involves simulating URL changes and verifying that the correct microfrontends are activated and deactivated. Libraries like single-spa-react
provide helper functions for integration testing.
End-to-end tests: Test the entire application flow using tools like Cypress or Selenium.
Performance tests: Measure load times, bundle sizes, and other performance metrics to ensure optimal performance.
Debugging a single-spa application can be challenging due to its distributed nature. Useful techniques include:
Browser developer tools: Use the browser’s developer tools (Network tab, Console tab) to inspect network requests, check for errors, and monitor performance.
Logging: Add logging statements to your microfrontends to track their lifecycle events and identify potential issues.
Source maps: Enable source maps during your build process to debug your code in the browser.
Debuggers: Use a debugger (e.g., Chrome DevTools) to step through your code and identify the root cause of errors.
Security is paramount. Consider the following:
Input validation: Sanitize user inputs within your microfrontends to prevent cross-site scripting (XSS) attacks.
Authentication and authorization: Implement proper authentication and authorization mechanisms to secure access to your applications. Consider using a central authentication service.
HTTPS: Use HTTPS to secure communication between the browser and your application.
Regular security audits: Perform regular security audits to identify and address potential vulnerabilities.
Migrating from other microfrontend approaches (e.g., iframes, manual JavaScript loading) to single-spa involves refactoring your existing code to fit the single-spa lifecycle model. This includes creating the bootstrap
, mount
, and unmount
functions for each of your applications, and configuring the single-spa router. The complexity depends on the existing architecture. Start with a small, self-contained component, migrate it to single-spa, and gradually migrate the remaining components.
A monorepo (a single repository containing multiple projects) can simplify the development and management of a single-spa application. Tools like Nx, Turborepo, or Lerna can help manage the monorepo. The key is to configure your build process (e.g., using workspaces in Webpack or similar features in other build tools) to build each microfrontend independently within the monorepo. This enables efficient dependency management and shared code between your microfrontends.
We welcome contributions to single-spa! If you find a bug, have a feature request, or want to improve the documentation, please follow these steps:
Report issues: Use the issue tracker on the official single-spa GitHub repository to report bugs or request new features. Provide clear and concise descriptions of the problem, including steps to reproduce it.
Submit pull requests: Fork the single-spa repository, make your changes, and submit a pull request. Ensure your code follows the existing coding style and includes thorough tests. Clearly describe the changes in your pull request.
Improve documentation: If you find the documentation unclear or missing information, submit a pull request to improve it. Clear, concise, and well-organized documentation is essential for the success of any open-source project.
Follow the contribution guidelines: Review the contribution guidelines in the single-spa repository for details on code style, testing, and other requirements.
The single-spa community is active and helpful. Here are some places to get support:
GitHub Issues: Use the GitHub issue tracker for bug reports and feature requests. While primarily for bugs and features, many users ask questions there, and the maintainers often respond.
Stack Overflow: Search Stack Overflow for answers to common single-spa questions. If you can’t find an answer, ask a new question with a clear title and description. Be sure to tag your question with single-spa
.
Other Forums (if applicable): Check for other relevant communities or forums where single-spa users might congregate. This can vary over time; check the official single-spa website for current links.
This section will be updated periodically with answers to commonly asked questions. In the meantime, check the single-spa documentation for comprehensive information. Common questions often revolve around:
How to integrate with specific frameworks? The documentation provides many examples, but questions about framework-specific nuances often arise.
Troubleshooting routing issues? Problems with the activeWhen
function and URL matching are common.
Optimizing application loading times? Questions related to code-splitting, lazy loading, and caching are frequent.
Handling errors? Questions regarding proper error handling in different parts of the application lifecycle often appear.
Here are some common issues encountered when working with single-spa and potential solutions:
Application not mounting: Check your activeWhen
function to ensure it’s correctly determining when the application should be active. Verify that your application’s lifecycle functions (bootstrap
, mount
, unmount
) are correctly exported and functioning. Inspect the browser’s console for errors.
Routing problems: Make sure your application’s routes are correctly defined and do not conflict with other applications’ routes. Double-check your activeWhen
function logic.
Unexpected behavior: Ensure that your microfrontends are not interfering with each other. Verify that global variables or event listeners are not causing conflicts. If possible, try isolating the problematic microfrontend to determine the cause of the error.
Performance issues: Analyze your application’s performance using browser developer tools to identify bottlenecks. Optimize your code, reduce bundle sizes, and implement caching strategies. Consider using a performance monitoring tool.
If you encounter issues not covered here, refer to the single-spa documentation or the community forums for assistance. Remember to provide as much detail as possible when reporting problems, including your setup, code snippets, and error messages.
Microfrontend: An independently deployable unit of a larger frontend application. In single-spa, this is often a single-page application (SPA) built with a framework like React, Angular, or Vue.
Root config: The main JavaScript file that configures single-spa, registering all the microfrontends and their lifecycle functions.
Lifecycle functions: The bootstrap
, mount
, and unmount
functions that define how a microfrontend interacts with single-spa.
activeWhen
function: A function that determines whether a microfrontend should be active based on the current URL or other criteria.
Parceling: The process of creating separate bundles (often using tools like Webpack or Parcel) for each microfrontend.
Lazy loading: The technique of loading a microfrontend only when it’s needed, improving initial load times.
Dynamic import: The import()
statement, used to lazy-load JavaScript modules.
Custom props: Data that can be passed from the root config to a microfrontend’s lifecycle functions.
This section provides a detailed reference of the single-spa API. It includes:
registerApplication(name, app, activeWhen, customProps)
: Registers a new microfrontend with single-spa. Details on each parameter are provided elsewhere in this manual.
start()
: Starts the single-spa routing and lifecycle management.
unregisterApplication(name)
: Unregisters a previously registered microfrontend.
addErrorHandler(handler)
: Adds a global error handler for single-spa.
utils
module: Provides various utility functions for single-spa (e.g., isAllApplicationsReady
, getMountedApps
).
Refer to the official single-spa documentation for the most up-to-date and complete API reference.
This section contains examples showcasing various aspects of single-spa, including:
Basic examples: Simple examples demonstrating the core concepts of single-spa.
Integrating with different frameworks: Examples of building microfrontends with React, Angular, Vue, and other frameworks.
Advanced routing scenarios: Examples showing how to handle complex routing using activeWhen
.
Custom lifecycle functions: Examples showcasing the creation and usage of custom lifecycle functions to extend single-spa’s functionality.
Performance optimization techniques: Examples illustrating strategies for improving load times and reducing bundle sizes.
This section details how to migrate your single-spa application from older versions to the latest version. This includes:
Breaking changes: A list of any breaking changes introduced in newer versions, along with guidance on how to update your code accordingly.
Deprecations: Information on deprecated APIs and their replacements.
Upgrade instructions: Step-by-step instructions for upgrading your project to the latest version of single-spa.
Always refer to the official release notes for the most accurate information on upgrading your single-spa application. Carefully review any breaking changes before upgrading to avoid unexpected problems. Consider testing thoroughly after upgrading to ensure everything functions as expected.