Serving our single page application using Cloudflare Workers

Or Kaplan
Remitly Israel (formerly Rewire)
5 min readDec 6, 2020

Here’s a new and efficient way to utilize Cloudflare Workers to serve your web application. Read more to see how Rewire managed to optimize its web application serving in no time.

Photo by Anthony Riera on Unsplash

For a long time now, our focus at Rewire has been on optimizing the build, deployment, and delivery of our single page web application. Our top priorities are to (1) ease the development process and (2) to serve our customers with maximal performance.

In this blog post, we will demonstrate how we utilize Cloudflare Workers to serve our web application.

History

The motivation

As a young startup, Rewire’s R&D team had to move fast and create an MVP as quickly as possible. It started with one service that served both the API and the web application, which is a set of static files served as a Single Page Application (SPA). When I joined rewire about two years ago, the R&D team was focused on breaking the monolith into API and SPA services. The SPA was built into a docker image and served via an Nginx server that runs on top of a Kubernetes cluster. While this structure did simplify our development process, it was not enough — we wanted to be resilient to outages in our Kubernetes cluster. We were sure that we can do better.

Diagram of the previous CI process serving using Nginx

Moving to serverless

Going back to the drawing board, we had a thought: Why not go serverless? We were already utilizing Cloudflare Workers for a few small-scale projects and it seemed like a great match. Cloudflare Workers supports a set of really cool features like zero cold start, out of the box CDN, caching, a great development experience, and fast deployments, which were very important for this project.

Diagram of the new solution using Cloudflare Workers

Our design

The design is pretty simple: Use the same process that builds the web app and pack it into a docker image. But instead of publishing the docker image into the container registry, copy the files to a Google Storage bucket that will be accessed by the Cloudflare worker. Then we intercept the requests, fetch the static files from the bucket, and serve them to the client. Sounds simple, right?

The implementation we suggest is pretty common: We host our code on a GitLab instance. Each time a developer pushes a commit to the relevant branch, we run the following pipeline using GitLab CI:

  1. Fetch the code
  2. Build — fetch all relevant npm packages and run Webpack to build the web application
  3. Pack — store the files in a docker image
  4. Publish the docker image to the registry
  5. Deploy the image to our Kubernetes cluster using Argo rollout

In the new design, we replaced two major steps (deploy and publish) with one new step that copies the static files directly from the docker image and loads them into the relevant bucket. That’s it, now the Cloudflare worker can serve the files from the bucket.

Serving the index

The entry point to the application is an index HTML file. For every new release, this file loads a different bundle of javascript files, CSS files, images, and more.

Our application doesn’t directly request the index.html file. Instead, we have to serve it from the root /. When we first see a request to the root path, we fetch the index file from the bucket and cache it in the worker key-value store to boost performance.

Fetch, Cache and Serve the index file

We make sure to clean the storage and fetch a new index file with each fresh deployment, in order to populate the index file when serving the next request.

Purging the index file upon deployment

This technique has amazingly cut the serving time of the index file by more than 80% — from 250ms to sub 50ms! But, it came with a price, which I’ll describe later on.

Utilizing worker cache

As you know, low latency is not the key feature of object store. In order to ensure our app’s fast loading times, we have decided to utilize the built-in cache of the Cloudflare worker. To do so, we had to push the resource to the cache for every request.

Utilizing Cache

Deployment Gotcha

While testing the project in production, we encountered a deployment issue. Each index file refers to a list of bundles that are replaced on each deployment. The bundle file name contains a hash that is updated on each deployment. Hence, if the client fetches the index, and then the deployment replaces the folder, the worker will return 404 for the resources. To overcome this issue, we used the following technique:

  1. Utilize cache to return the files
  2. In case the cache is disabled or invalidated, we have to find another solution. So, we are now saving two live versions: The first one is the current, live version, and the second one is what we previously had for backup. During deployment, we copy the live version to the backup and replace it. In case we can’t find a resource in the live version, we search it on the backup, where it should exist
Blue-Green Deployment using Cloudflare Workers

Summary

Using Cloudflare workers, we were able to build a high availability solution in record time. It required us to play with HTTP headers, cache directly from the code all while providing great flexibility to build the serving mechanism as we wished. Running on the edge serves in over 200 cities across the world, making it much more performant than our previous Kubernetes based solution. In other words, we basically got CDN for free using workers.

Being a customer-centric company, we are now able to provide our customers with a much better experience and a more stable app.

The next post in the series will demonstrate how this technique can be used to build an infinite number of staging environments for developers to sign-off their features with product managers, and how it can be used for quick end-to-end testing using the testim.io framework.

--

--