Skip to main content

Turning the Page: Replacing single-spa with CollageJS

Updated 4 min readMarkdown
Part of seriesSingle-SPA & Svelte: My Journey
Turning the Page:  Replacing single-spa with CollageJS

It's been a while since I don't write about the micro-frontends topic, mostly due to other responsibilities and then other interests.

However, today I'm back with this very interesting topic: Micro-frontends.

As many people know already, I created only short 4 years ago the only plug-in in existence for Vite and the excellent single-spa routing library. The plug-in, named vite-plugin-single-spa was highly successful, serving up to 25,000+ downloads from npmjs.com weekly with less than 10 bugs reported.

This plug-in enabled many of us to opt-in to move modern projects to single-spa because we were unwilling to give up Vite. In short: The plug-in was a complete success.

Nothing is forever, however. For people that don't know, single-spa creator passed away in November 2025, and no core team was left behind to perpetuate and evolve the single-spa environment.

We, single-spa users were now left adrift with a library that most likely won't be maintained ever again. It is time to switch to a different technology.

Deciding the Switch

For me, the decision was simple because my selection of single-spa back in 2022 was also simple: Simple router with minimal restrictions, easy micro-frontend interface (the lifecycle functions) and everything else was opt-in, such as the use of import-map-overrides.

Now we face extinction, and moving to the previously-studied-and-discarded micro-frontends solution was not an option: All their problems and dislikes back then are still there; the alternatives haven't changed. I still didn't want the complex Piral requiring a CDN and online accounts, or Module Federation being only a micro-frontend solution if we are talking same-framework projects (my projects aren't same-framework projects), etc.

In light of this, I decided to contribute an evolution of single-spa: CollageJS.

CollageJS: Inception

After working with the amazing Svelte framework, single-spa and my Vite plug-in, I've come to realize I had a lot of margin for improvement. This improvement is CollageJS' main objective.

CollageJS has taken the core concept of the single-spa parcel and has evolved it to make it the only thing needed. In CollageJS, this concept is named Core Piece.

Core pieces are the micro-frontends and they can be as big or as small as we want them to be. These pieces are defined in TypeScript as an interface:

export interface CorePiece<TProps, TMeta> {
  mount: MountFn<TProps> | MountFn<TProps>[];
  relocate?: RelocateFn | RelocateFn[];
  update?: UpdateFn<TProps> | UpdateFn<TProps>[];
  meta?: TMeta & {
    remountable?: boolean;
    relocatable?: boolean;
  };
}

The only required method is mount. This keeps the creation of pieces (micro-frontends) super easy.

Improvements Over single-spa

Because single-spa had struggled over the years to keep up, it is no surprise to know that there were many small and not-so-small bugs and issues at its core. I have made sure CollageJS overcomes them. Just to name a few improvements:

  • Full TypeScript support
  • Multiple copies of pieces can coexist at the same time
  • All framework adapters support update of properties and a framework-specific component to mount core pieces
  • No name collisions between adapter component properties and the properties of the micro-fronted being mounted (the core piece)
  • Idempotent unmount lifecycle

If you would like to see a more complete list of improvements, head to the migration guide, at the bottom.

Improved Developer Experience

Have you ever worked with import maps? If you have, you understand that their use is quite convenient but makes us developers repeat module names around, especially because most of the time, we need to externalize the modules whenever we bundle our projects.

With the new set of Vite plug-ins for CollageJS, our developer experience is improved. In root projects (the project that loads other projects and provides the import map):

  • Import maps are auto-externalized
  • We get the import map overriding tool set up

In core piece projects:

  • Import maps are auto-externalized while in dev mode automatically
  • CSS is mounted for us

We also get a brand new import map-overriding solution with a modern user interface and more advanced features:

@collagejs/imo user interface

Migrating Projects

Migrating projects from single-spa to CollageJS is incredibly fast. Generally speaking, most projects take only about 10 minutes of work. This means that migrating an entire system, even if comprised of dozens of projects, can migrate in one day with barely any visible difference.

If you're in the single-spa boat and want out, just follow the migration guide.

Test Drive CollageJS

If you're feeling curious about how this works, it can be tested in minutes by cloning the template repository, either with GitHub's template funcionality, or with the degit command:

npx degit https://github.com/collagejs/root-template.git

Feel free to experience micro-frontends with full Vite v8 and TypeScript support. Happy coding!

Having mount as the only required method keeps the entry bar low, and an idempotent unmount is a good call. Two things teams usually run into with import map setups: version drift when two pieces externalize the same library at different versions, and one piece failing to mount and blanking the shell. Does the root project give an error boundary or fallback around a failed mount?

Hello! The mounting project (be it root or not) will always throw any errors during the evaluation of the mount lifecycle chain. The shell blanking is a possibility. Error boundaries are framework things that developers may opt to use, but since they are framework-specific CollageJS doesn't include them by default. Fallbacks are in the same category: Not included because it is up to developers whether to use fallbacks.

Generally speaking, developers/teams should always externalize using the import map entries. The Vite plug-in @collagejs/vite-aim (the hidden plug-in) does this externalization using the root project's import map, minimizing the possibility of errors due to version differences.

More from this blog