• 2 min read

Client Side Routing with `single-spa`

I was surprised when client side routing with single-spa didn't work as I expected it to. Instead, I saw full page reloads anytime I clicked a link to navigate around the UI. Thankfully the solution isn't too complicated.

The single-spa micro frontend package provides easy to use routing out of the box, especially when accompanied by single-spa-layout.

Using the layout package, routes are defined with <route> element children as part of the larger <single-spa-router>:

<single-spa-router mode="history">
  <route path="/app1">
    <application name="app1"></application>
  </route>
  <route path="/settings">
    <application name="settings"></application>
  </route>
</single-spa-router>

Routing Mode

Two different modes are available for routing, hash or history (default being the latter). These correlate directly to keys from the browser's Location object, where hashreferences the key of the same name and history looks at pathname.

Hash routing works with URLs of this pattern:

  • https://my.app/#/app1
  • https://my.app/#/app1/route/nested
  • https://my.app/pathname/#/hash/route/nested

Whereas history-based routing doesn't require the hash:

  • https://my.app/app1
  • https://my.app/app1/route/nested
  • https://my.app/pathname/hash/route/nested

Based on my experience, the hash method of routing requires the least work because it works without intervention. No additional listeners or callbacks required. The historyrouting results in cleaner URLs though, so I chose that direction.

History Routing

Based on the project I was working on, creating an <a href="..."> link caused the browser to reload the new page instead of using browser History APIs to dynamically update the current URL and push to history.

Thankfully, single-spa provides navigateToUrl to properly utilize client side routing.

It can be used a couple different ways:

// Directly navigate to a new URL.
singleSpa.navigateToUrl('/new-url');
// Attach to all link `<a>` elements in the DOM.
singleSpa.navigateToUrl(document.querySelector('a'));
document.querySelector('a').addEventListener(singleSpa.navigateToUrl);

Or the final way, which I used in my React frontend. Adding the callback directly to the link's onClick event:

import { navigateToUrl } from 'single-spa';
...
<a href="/app1" onClick={navigateToUrl}>

Attaching directly to the link element provides transparency into exactly which links will trigger the single-spa client side routing. This allows me to be selective about which links I route internally and keep a usability-friendly <a> link tag so browsers properly identify the element's purpose. Expected behaviors, like middle clicks to open a link in a new tab, are preserved through this method as well.

This approach could also be wrapped into a shared React component, packaged in a single-spa Parcel, and easily reused across multiple React micro frontends.


Anson Lichtfuss

Written by Anson Lichtfuss, a frontend engineer building useful, beautiful interfaces in Seattle.