Frequently Asked Questions
What does single-spa do?
single-spa is a top level router. When a route is active, it downloads and executes the code for that route.
The code for a route is called an "application", and each can (optionally) be in its own git repository, have its own CI process, and be separately deployed. The applications can all be written in the same framework, or they can be implemented in different frameworks.
Is there a recommended setup?
Yes, here is the documentation for our recommended setup.
Should I have a parent/root app and children apps?
No. We strongly encourage that your single-spa-config or root application does not use any JavaScript UI frameworks (React, Angular, Angularjs, Vue, etc). In our experience a plain JavaScript module is best for the single-spa-config and only the registered applications actually use UI frameworks (Angular, React, Vue, etc).
Why? You end up creating a structure that has all the disadvantages of microservices without any of the advantages: your apps are now coupled together and you have to change multiple apps at the same time in order to make updates. Good microservices are completely independent, and this pattern breaks that.
What is the impact to performance?
When setup in the recommended way, your code performance and bundle size will be nearly identical to a single application that has been code-split. The major differences will be the addition of the single-spa library (and SystemJS if you chose to use it). Other differences mainly come down to the difference between one (webpack / rollup / etc.) code bundle and in-browser ES modules.
Can I have only one version of (React, Vue, Angular, etc.) loaded?
Yes, and it's highly recommended you do so! Using the recommended setup, you configure your import map so that your library is defined only once. Then, tell each application to not bundle that library; instead, the library will given to you at runtime in the browser. See webpack’s externals (and other bundlers have similar options) for how to do this.
You do have the option of not excluding those libraries (for example if you want to experiment with a newer version or a different library) but be aware of the effect that will have on user's bundle sizes and application speed.
What are import maps?
Import maps improve the developer experience of in-browser ES modules by allowing you to write something like import React from "react"
instead of needing to use an absolute or relative URL for your import statement. The same is also true of importing from other single-spa applications, e.g. import {MyButton} from "styleguide"
. The import-map spec is currently in the process of being accepted as a web standard and at the time of writing has been implemented in Chrome, and a polyfill for browsers >= IE11 has been implemented by SystemJS >= 3.0. Also see the recommended setup
How can I share application state between applications?
The primary means of communicating between applications is cross microfrontend imports. This allows you define a public interface for a microfrontend that others can use. You may expose functions, data, components, stores, or anything else from any microfrontend to be available in any other.
We recommend that each application manage as much of its own state as possible so that your applications remain independently deployable without the risk of breaking each other. Generally, it’s better to make an API request for the data that each app needs, even if parts of it have been requested by other apps. If you've split your applications well, there will end up being very little application state that is truly shared — for example, your friends list has different data requirements than your social feed.
The list below shows some common practices:
- Create a shared API utility microfrontend that caches fetch/XHR requests and their responses. All microfrontends call into the API microfrontend when making a request, so that the microfrontend can control whether to refetch the data or not.
- Create a shared Auth utility microfrontend that exposes a
userCanAccess
function for other microfrontends to use when checking permissions. The auth module may also include other exports such as the logged in user object, auth tokens, etc. - Export shared state from the public interface of your microfrontend so that libraries can import it. For values that change over time, Observables (RxJS docs) can be useful. Create a ReplaySubject so that you can push new values out to all subscribers at any time.
- Use custom browser events to communicate. Fire them on the window in one microfrontend, and listen to the event in a different microfrontend.
- Use cookies, local/session storage, or other similar methods for storing and reading that state. These methods work best with things that don't change often, e.g. logged-in user info.
Should I use frontend microservices?
If you’ve ran into some of the headaches a monolithic repo has, then you should really consider it.
In addition, if your organization is setup in a Spotify-type model (e.g. where there are autonomous squads that own full-stack features) then microservices on the frontend will fit very well into your setup.
However, if you’re just starting off and have a small project or a small team, we would recommend you stick with a monolith (i.e. not microservices) until you get to the point that scaling (e.g. organizational scaling, feature scaling, etc.) is getting hard. Don’t worry, we’ll be here to help you migrate when you get there.
Can I use more than one framework?
Yes. However, it’s something you’ll want to consider hard because it splits your front-end organization into specialities that aren’t compatible (e.g. a React specialist may have problems working in an Angular app), and also causes more code to be shipped to your users.
However, it is great for migrations away from an older or unwanted library, which allows you to slowly rip out the code in the old application and replace it with new code in the new library (see Google results for the strangler pattern).
It also is a way to allow large organizations to experiment on different libraries without a strong commitment to them.
Just be conscious of the effect it has on your users and their experience using your app.
What is the developer experience (DX) like?
If you're using the recommended setup for single-spa, you'll simply be able to go to your development website, add an import map that points to your locally-running code, and refresh the page.
There's a library that you can use, or you can even just do it yourself - you'll note that the source code is pretty simple. The main takeaway is that you can have multiple import maps and the latest one wins - you add an import map that overrides the default URL for an application to point to your localhost.
We're also looking at providing this functionality as part of the Chrome/Firefox browser extension.
Finally, this setup also enables you to do overrides in your production environment. It obviously should be used with caution, but it does enable a powerful way of debugging problems and validating solutions.
As a point of reference, nearly all developers we've worked with prefer the developer experience of microservices + single-spa over a monolithic setup.
Can each single-spa application have its own git repo?
Yes! You can even give them their own package.json, webpack config, and CI/CD process, using SystemJS to bring them all together in the browser.
Can single-spa applications be deployed independently?
Yes! See next section about CI/CD.
What does the CI/CD process look like?
In other words, how do I build and deploy a single-spa application?
With the recommended setup, the process generally flows like this:
- Bundle your code and upload it to a CDN.
- Update your dev environment's import map to point to the new URL. In other words, your import map used to say
"styleguide": "cdn.com/styleguide/v1.js"
and now it should say"styleguide": "cdn.com/styleguide/v2.js"
Some options on how to update your import map include:
- Server render your
index.html
with the import map inlined. This does not mean that your DOM elements need to all be server rendered, but just the<script type="systemjs-importmap">
element. Provide an API that either updates a database table or a file local to the server. - Have your import map itself on a CDN, and use import-map-deployer or similar to update the import map during your CI process. This method has a small impact on performance, but is generally easier to setup if you don't have a server-rendered setup already. (You can also preload the import map file to help provide a small speed boost). See example travis.yml. Other CI tools work, too.
Create React App
Tutorial video: Youtube / Bilibili
If you are starting from scratch, prefer using create-single-spa instead of create-react-app. A project using CRA must be altered before it can be used as a single-spa application. CRA provides many features out of the box, and outputs "monolith" apps by default. CRA does not support extending its configuration so we cannot provide official support for using it with single-spa. In order to continue using CRA, your options are to:
-
Use a third-party tool to extend CRA, such as:
-
react-app-rewired + this Gist as a starting point for your own custom config
-
Remove
react-scripts
and then runcreate-single-spa
on your project to merge create-single-spa's package.json with yours. Runyarn start
and fix webpack configuration errors until it's working. -
Generate a project with create-single-spa and migrate the React code over.
The single-spa webpack configs only provide basic functionality and not all the same features that CRA does. You may be required to add & configure more options, plugins, or loaders for non-JavaScript files. This may require advanced knowledge across bundlers, toolchains, plugins, etc.
For additional reference here is a list of some changes you will need to make to your webpack config:
- Remove Webpack optimizations block, because they add multiple webpack chunks that don't load each other
- Remove html-webpack plugin
- Change
output.libraryTarget
toSystem
,UMD
, orAMD
.
Code splits
Single spa supports code splits. There are so many ways to code split we won't be able to cover them all, but if you're using the recommended setup with webpack you'll need to do at least two things:
-
Set the
__webpack_public_path__
dynamically so webpack knows where to fetch your code splits (webpack assumes they are located at the root of the server and that isn't always true in a single-spa application). Both solutions below should be the very first import of your application in order to work.- For SystemJS >= 6, use systemjs-webpack-interop:
import { setPublicPath } from "systemjs-webpack-interop";
setPublicPath("name-of-module-in-import-map");- For SystemJS 2-5: Find a code example here
-
Set either
output.jsonpFunction
oroutput.library
to ensure that each app's webpack doesn't collide with other apps' webpack.jsonpFunction
is preferred.
For more information about webpack configuration and single-spa, see the recommended setup.
Does single-spa require additional security considerations?
No. single-spa does not add, deviate, or attempt to bypass any browser JavaScript security measures. The security needs of your applications are the same as if you did not use single-spa.
Outside of that, web applications may use the following resources that have their own security considerations that you may need to become familiar with:
- ES6 module dynamic imports
- Webpack-based applications use Webpack's implementation of dynamic imports
- Cross-Origin Resource Sharing (CORS)
- Content Security Policy (CSP)
- module imports specifically relate to CSP
script-src
- module imports specifically relate to CSP
- Subresource Integrity (SRI)
- Import-maps are also governed by CSP
How do I write tests for single-spa?
See the documentation for unit and E2E tests.