Not so micro-frontends: Building a Reverse Proxy

Oded Welgreen
asurion-product-development
6 min readJan 9, 2020
A lot different types of mugs, presented in a cupboard together
Photo by Eric Prouzet on Unsplash

Conway’s law states that organizations are constrained to produce designs that echo the communication structure in that organization. At Soluto, we use the Journey Team model of small cross-functional autonomous teams that are empowered to build products and features. This helps us build exceptional products, with a focus on providing value for our users.

However, we are not above the law, and eventually the products that we built started to echo our organizational structure — small, independent experiences and features, that at times feel inconsistent and fragmented.

In an effort to redesign some of our web-based products, we decided it was time to make them feel more consistent — in other words, make all experiences feel like they are part of the same service.

So here was our challenge — we had four different teams developing four different web experiences. Some already had an existing codebase, CI/CD pipelines and hosting solution, so we wanted to find a solution that will incur minimal changes for those teams. Our objective was to make those four experiences feel like part of the same service. We broke it down to these key results:

  1. All experiences should be served from the same domain.
  2. There should be a single authentication mechanism. When a user signs into one experience, they should also be signed in for the rest.
  3. The experiences should have the same look & feel.

We were almost satisfied with this list. We felt there was one basic goal which was implicit, so we decided to make it very explicit and bring it front and center —

Teams should remain as independent as possible.

This means that teams keep end to end responsibility over their experiences, which includes their codebase, CI/CD pipelines, hosting and any other technical aspect of their experience.

Just to make it interesting, the deadline to launch was very challenging. Where’s the fun otherwise right?

“I have no idea what I’m doing” dog

Time to get working

In this post I will focus on the first goal — all experiences should be served from the same domain.

Well, obviously the solution is Micro Frontends right? right? guys?

This was our initial thought, but after doing some research we couldn’t find a solution that wouldn’t require a lot of refactoring from the rest of teams, or couple them to a specific framework and/or deployment infrastructure. So we decided to increase the order of magnitude.

Mini Frontends

So how can we serve all of the experiences from the same domain if they are not part of the same web-application? The solution we chose was to build a reverse proxy.

A reverse proxy is a type of proxy server that retrieves resources on behalf of a client from one or more servers.
- From Wikipedia

All requests to our domain will be handled by a piece of logic, which will proxy requests based on the path. For example, say we have three websites — a photos experience, a streaming experience, and a default experience:

  • All requests to soluto.com/photos should be proxied to photos experience website
  • All requests to soluto.com/streaming should be proxied to our streaming experience website
  • All requests to all other paths should be proxied to the default website

This would mean that teams don’t have to change (almost) anything. It does not assume anything about the different websites, other than being able to handle HTTP requests.

Reverse proxy architecture diagram

Let’s talk details

How many ways are there to implement a reverse proxy? The scientific answer is, unsurprisingly, “lots”. To help us narrow it down, we set some requirements:

  • As “managed” a solution as possible
  • Highly available
  • Minimal impact on performance

Here at Soluto, we make heavy use of AWS and love serverless solutions. Taking everything into account, Lambda@Edge answered all our requirements. It’s a managed solution, globally distributed and runs close to the user with minimal latency. Perfect!

So let’s start implementing. Sounds easy right? (spoiler: nope)

First pass

Let’s continue the example from above. We have three websites — photos, streaming and the default website.

For each request, we’ll check if the first segment of the path corresponds to photos or streaming. If so, we’ll proxy the request to that website, if not we’ll proxy it to the default website.

First pass flowchart

Done. Ship it.

Launch and crash

Gotcha #1

Say photos requests the resource /index.js . The browser will send request to soluto.com/index.js . However, the correct path is soluto.com/photos/index.js . One solution for this issue is to request all assets using a relative path, but this will force all teams to change their existing codebase in a lot of places.

So we looked at the request and tried to find a way to identify that it was coming from /photos . Turns out there is! We can use the referer header (misspelled in the original RFC).

Second pass

Let’s revise — if the request does not start with /photos or /streaming , but one of those paths is stated in the referer — proxy to the corresponding website. Otherwise proxy to the default website.

Second pass flowchart

Gotcha #2

What happens if photos has a link to the default website? The reverse proxy will receive a request to / which is fine, but the referer will contain /photos , and so the request will be proxied to photos instead.

We solved this by adding another mapping, /default that should be proxied to the default website. All links to the default website from the other websites should use that path instead of just /.

Gotcha #3

When we first deployed the reverse proxy and websites, it seemed that internal routes within the websites were not working. After doing some digging we found the problem. All websites were written in React, and used thereact-router package for internal routing. Say photos has an internal route /about , since the react-router lives on the client-side, the actual path it sees is /photos/about .

The solution we opted for this issue is to set the basename on the Router component like so:

<Router basename="/photos">

Done, ship it

There were some more gotchas, but for the sake of brevity, I focused on the main ones. We’ve since deployed the reverse proxy and it has handled millions of requests, so far without a hitch.

Nothing to see here, please disperse (Naked Gun)

I hand-waved some details in this post, I’ll admit. The reverse proxy is only one part of a more complex system. In future posts, I plan to dive into some more details and describe how we achieved the rest of our goals . If you have any feedback or would like me to expand on something, let me know.

We love the Journey Team model and see its advantages daily in our products, our work and in personal development as well. It has proven to drastically improve our time-to-market, sense of ownership and helps us feel close to our users.

Like everything in life though, it is not without its challenges. In this post, I described some of those challenges and our attempt to tackle them, in our constant search to improve. I hope you enjoyed the article!

--

--

Oded Welgreen
asurion-product-development

Dad, partner, musician, gamer, eternally curious. Software Engineer @ Spinach.io