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 hash
references 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:
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 history
routing 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.