Version: 5.x
Layout Definition
A layout is a combination of HTMLElements, routes, and single-spa applications. Layout is defined statically in your root config to handle your top level routes and dom elements. Single-spa-layout should not be used outside of the root config; instead, a UI framework (React, Angular, Vue) should handle layouts within the applications.
You may define layouts as either HTML templates or JSON objects. Defining in JSON is supported for organizations who prefer storing their layout definitions in a database instead of code. Both HTML and JSON layouts have the same feature set. However, storing layouts in code is generally preferred and encouraged by default. If you’re just getting started with single-spa-layout, we encourage using an HTML template.
Once you define your layout, you should constructRoutes
, constructApplications
, and constructLayoutEngine
.
You may define HTML layouts either within your root config’s index.html file, or within a javascript string that is parsed as HTML. We generally encourage defining the layout within your root config’s index.html file.
To define a layout within your index.html file, create a <template id="single-spa-layout">
element that contains your layout. Within the template, add a <single-spa-router>
element, along with any routes, applications, and dom elements.
Note that HTMLElements defined in your layout are static - there is no way to forcibly re-render or change them.
<!-- index.ejs -->
<html>
<head>
<template>
<single-spa-router>
<div class="main-content">
<route path="settings">
<application name="settings"></application>
</route>
</div>
</single-spa-router>
</template>
</head>
</html>
// You can pass in an HTML string, too, in the browser
const routes = constructRoutes(`
<single-spa-router>
<div class="main-content">
<route path="settings">
<application name="settings"></application>
</route>
</div>
</single-spa-router>
`);
// With a properly configured bundler, you can import the html as a string from another file
import layout from './microfrontends-layout.html';
const routes = constructRoutes(layout);
You may define your layout as JSON, including routes, applications, and arbitrary dom elements.
const routes = constructRoutes({
"routes": [
{ "type": "route", "path": "settings", "routes": [
{ "type": "application", "name": "settings" }
]}
]
});
A layout element is an HTMLElement or JSON object that represents either a dom node, route, or application.
The template element is only used when defining the layout as HTML. Its purpose is to prevent its contents from being displayed by the browser, since the layout definition should not be visible to user.
<template>
<!-- Define your layout here -->
<single-spa-router></single-spa-router>
</template>
Note that <template>
elements are not fully supported in IE11. However, you do not need to polyfill template elements in order to use them in single-spa-layout. Instead, simply add style="display: none;"
to the template to prevent its contents from being displayed in IE11.
<template style="display: none;">
<!-- Define your layout here -->
<single-spa-router></single-spa-router>
</template>
The single-spa-router
element is required as the top level container of your layout. All attributes are optional.
<single-spa-router mode="hash|history" base="/" disableWarnings></single-spa-router>
{
"mode": "hash|history",
"base": "/",
"disableWarnings": false,
"containerEl": "#container",
"routes": []
}
Attributes
mode
(optional): A string that must behash
orhistory
that defaults tohistory
. This indicates whether the routes should be matched against the Location pathname or hash.base
(optional): A string URL prefix that will be considered when matching route paths.disableWarnings
(optional): A boolean that turns of single-spa-layout’s console warnings when the elements provided are incorrect.containerEl
(optional): A string CSS Selector or HTMLElement that is used as the container for all single-spa dom elements. Defaults tobody
.
The route
element is used to control which applications and dom elements are shown for a top-level URL route. It may contain HTMLElements, applications, or other routes. Note that the route path is a URL prefix, not an exact match.
<route path="clients">
<application name="clients"></application>
</route>
<route default>
<application name="clients"></application>
</route>
{
"type": "route",
"path": "clients",
"routes": [
{ "type": "application", "name": "clients" }
],
"default": false
}
Attributes
Routes must either have a path or be a default route.
routes
(required): An array of children elements that will be displayed when the route is activepath
(optional): A path that will be matched against the browser’s URL. The path is relative to its parent route (or the base URL). Leading and trailing/
characters are unnecessary and are automatically applied. Paths may contain “dynamic segments” by using the:
character ("clients/:id/reports"
). Single-spa-layout uses single-spa’s pathToActiveWhen function to convert the path string to an activity function. By default, the path is a prefix because it will match when any subroutes of the path match. See theexact
attribute for exact matching.default
(optional): A boolean that determines whether this route will match all remaining URLs that have not been defined by sibling routes. This is useful for 404 Not Found pages. A sibling route is defined as any route with the same nearest-parent-route.exact
(optional, defaults tofalse
): A boolean that determines whether thepath
should be treated as a prefix or exact match. Whentrue
the route does not activate if there are trailing characters in the URL path that are not specified in thepath
attribute.props
: An object of single-spa custom props that will be provided to the application when it is mounted. Note that these can be defined differently for the same application on different routes. You can read more about defining props within your HTML in the docs below.
The application
element is used to render a single-spa application. Applications may be contained within route elements, or may exist at the top level as applications that will always be rendered. A container HTMLElement will be created by single-spa-layout when the application is rendered. The container HTMLElement is created with an id
attribute of single-spa-application:appName
such that your framework helpers will automatically use it when mounting the application.
The same application may appear multiple times in your layout, under different routes. However, each application can only be defined once per-route.
<!-- Basic usage -->
<application name="appName"></application>
<!-- Use a named loader that is defined in javascript -->
<application name="appName" loader="mainContentLoader"></application>
<!-- Add single-spa custom props to the application. The value of the prop is defined in javascript -->
<application name="appName" props="myProp,authToken"></application>
// Basic usage
{
"type": "application",
"name": "appName"
}
// Use a single-spa parcel as a loading UI
// You may also use Angular, Vue, etc.
const parcelConfig = singleSpaReact({...})
{
"type": "application",
"name": "appName",
"loader": parcelConfig
}
// Use an HTML string as a loading UI
{
"type": "application",
"name": "appName",
"loader": "<img src='loading.gif'>"
}
// Add single-spa custom props
{
"type": "application",
"name": "appName",
"props": {
"myProp": "some-value"
}
}
Attributes
name
(required): The string application name.loader
(optional): An HTML string or single-spa parcel config object. The loader will be mounted to the DOM while waiting for the application’s loading function to resolve. You can read more about defining loaders in the docs belowprops
: An object of single-spa custom props that will be provided to the application when it is mounted. Note that these can be defined differently for the same application on different routes. You can read more about defining props within your HTML in the docs below.
The fragment
element is used to specify a dynamic server-rendered portion of the template. Fragments are commonly used to inline import maps, add dynamic CSS / fonts, or customize the HTML <head>
metadata. See sendLayoutHTTPResponse for more information about how fragments are rendered. Note that <fragment>
elements only have meaning in server templates, not browser-only templates.
<fragment name="importmap"></fragment>
<fragment name="head-metadata"></fragment>
The <assets>
element is used to specify the location of server-rendered application assets, including CSS and fonts. When server-side rendered, the <assets>
element is replaced by all the assets from the active applications on the page. Applications specify their assets as part of the renderApplication
function provided to the sendLayoutHTTPResponse function.
<assets></assets>
The <redirect>
element is used to specify route redirects. On the server side, this is done with res.redirect()
, which results in an HTTP 302 being sent to the browser. Within the browser, this is done by canceling navigation and then calling navigateToUrl().
Redirects are always defined with absolute paths. This means that nesting a <redirect>
inside of a route will not behave any differently than placing the redirect at the top level. All redirects should have full paths. Leading slashes are optional in those full paths.
<redirect from="/" to="/login"></redirect>
<redirect from="/old-settings" to="/login-settings"></redirect>
In JSON, redirects are defined as a top-level property:
{
"routes": [],
"redirects": {
"/": "/login",
"/old-settings": "/settings"
}
}
Arbitrary HTMLElements may be placed anywhere in your layout. You may define arbirary dom elements in both HTML and JSON.
single-spa-layout only supports updating DOM elements during route transitions. Arbitrary re-renders and updates are not supported.
DOM elements defined within a route will be mounted/unmounted as the route becomes active/inactive. If you define the same DOM element twice under different routes, it will be destroyed and recreated when navigating between the routes.
<nav class="topnav"></nav>
<div class="main-content">
<button>A button</button>
</div>
The format of dom nodes in JSON is largely based on the parse5 format.
Elements are defined with their nodeName
as the type
. HTML attributes are specified as the attrs
array, where each item is an object with name
and value
properties.
{
"type": "div",
"attrs": [
{
"name": "class",
"value": "blue"
}
]
}
Child nodes are specified via the "routes"
property.
{
"type": "div",
"routes": [
{
"type": "button"
}
]
}
Text Nodes are defined separately from the parent containers, as separate objects with type
set to #text
:
{
"type": "#text",
"value": "The displayed text"
}
Button with text:
{
"type": "button",
"routes": [
{
"type": "#text",
"value": "The button text"
}
]
}
Note that text nodes may not have routes
(children).
Comment Nodes are defined as objects whose type
is #comment
:
{
"type": "#comment",
"value": "The comment text"
}
Note that comments may not have routes
(children).
Single-spa custom props may be defined on both route
and application
elements. Any route props will be merged together with the application props to create the final props that are passed to the single-spa lifecycle functions.
In a JSON layout definition, you can define props with the props
property on your applications and routes:
import { constructRoutes } from 'single-spa-layout';
constructRoutes({
routes: [
{ type: "application", name: "nav", props: { title: "Title" } },
{ type: "route", path: "settings", props: { otherProp: "Some value" } },
]
})
Defining props on JSON objects is straightforward, as they are an object that can contain strings, numbers, booleans, objects, arrays, etc. However, defining complex data types in HTML is not as straightforward, since HTML attributes are always strings. To work around this, single-spa-layout allows you to name your props in the HTML, but define their values in javascript.
<application name="settings" props="authToken,loggedInUser"></application>
import { constructRoutes } from 'single-spa-layout';
const data = {
props: {
authToken: "fds789dsfyuiosodusfd",
loggedInUser: fetch('/api/logged-in-user').then(r => r.json())
}
}
const routes = constructRoutes(document.querySelector('#single-spa-template'), data)
The full API documentation for the constructRoutes
API explains the data
object in detail.
It is often desireable to show a loading UI when waiting for an application’s code to download and execute. Single-spa-layout allows you to define per-application loaders that will be mounted to the DOM while the application’s loading function is pending. It is possible to share the same loading UI for multiple applications.
A loading UI is defined as either an HTML string or as a parcel config object. HTML strings are best for static, non-interactive loaders, whereas parcels are best when you want to use a framework (Vue, React, Angular, etc) to dynamically render the loader.
Defining loaders via javascript objects is straightforward, as they are an object that can contain strings, numbers, booleans, objects, arrays, etc. However, defining complex data types in HTML is not as straightforward, since HTML attributes are always strings. To work around this, single-spa-layout allows you to name your loaders in the HTML, but define their values in javascript.
<application name="topnav" loader="topNav"></application>
<application name="topnav" loader="settings"></application>
import { constructRoutes } from 'single-spa-layout';
// You may also use Angular, Vue, etc.
const settingsLoader = singleSpaReact({...})
const data = {
loaders: {
topNav: `<nav class="placeholder"></nav>`,
settings: settingsLoader
}
}
const routes = constructRoutes(document.querySelector('#single-spa-template'), data)
The full API documentation for the constructRoutes
API explains the data
object in detail.
Support for route transitions is planned, but not yet implemented. If you have interest in this feature, please provide use cases, upvotes, and feedback in this tracking issue.
Default routes are routes that activate when no other sibling routes match the current URL. They do not have a URL path and may contain any combination of DOM elements and single-spa applications.
<single-spa-router>
<route path="cart"></route>
<route path="product-detail"></route>
<route default>
<h1>404 Not Found</h1>
</route>
</single-spa-router>
Default routes are matched against their sibling routes, which allows for nesting:
<single-spa-router>
<route path="cart"></route>
<route path="product-detail/:productId">
<route path="reviews"></route>
<route path="images"></route>
<route default>
<h1>Unknown product page</h1>
</route>
</route>
<route default>
<h1>404 Not Found</h1>
</route>
</single-spa-router>
Sibling routes are defined as those that share a “nearest parent route.” This means that they do not have to be direct siblings in your HTML/JSON, but can be nested within DOM elements:
<single-spa-router>
<route path="product-detail/:productId">
<div class="product-content">
<route path="reviews"></route>
<route path="images"></route>
</div>
<!-- The reviews and images routes are siblings, since they share a nearest parent route -->
<!-- The default route will activate when the URL does not match reviews or images -->
<route default>
<h1>Unknown product page</h1>
</route>
</route>
</single-spa-router>
When a single-spa application fails to load, mount, or unmount, it moves to SKIP_BECAUSE_BROKEN or LOAD_ERROR status. When in SKIP_BECAUSE_BROKEN status, often nothing is visible to the user and they won’t understand why the application is not showing. You can call unloadApplication to move the application back to NOT_LOADED status, which will cause single-spa to re-attempt downloading and mounting it. However, it is often desireable to show an error state when the application errors.
An error UI is defined as either an HTML string or as a parcel config object. HTML strings are best for static, non-interactive error states, whereas parcels are best when you want to use a framework (Vue, React, Angular, etc) to dynamically render the error state. The error UI will be shown whenever the application’s status is SKIP_BECAUSE_BROKEN or LOAD_ERROR.
Note that Error UI parcels are given a prop called error
that is the Error that caused the application to fail in loading/mounting.
Defining error uis via javascript objects is straightforward, as the string or parcel can be defined in an application object via the error
property:
{
"type": "application",
"name": "nav",
"error": "<h1>Oops! The navbar isn't working right now</h1>"
}
const myErrorParcel = singleSpaReact({...})
{
"type": "application",
"name": "nav",
"error": myErrorParcel
}
However, defining error uis in HTML is less straightforward, since HTML attributes are always strings and therefore can’t be a parcel config object. To work around this, error UIs are named in the HTML, but defined in javascript:
<template id="single-spa-layout">
<single-spa-router>
<application name="nav" error="navError"></application>
</single-spa-router>
</template>
const myErrorParcel = singleSpaReact({...})
const routes = constructRoutes(document.querySelector('#single-spa-layout'), {
errors: {
navError: myErrorParcel
// alternatively:
// navError: "<h1>Oops! The navbar isn't working right now</h1>"
}
})