Sapper on Cloud Run with static assets hosted on Firebase Hosting Architecture
This is a follow-up post to Sapper, Google Cloud Run, Continuous Deployment - A boilerplate template.
In this second part, we are going to optimize how static assets are hosted and delivered to the browser. The final architecture for this post is going to be like this:
Sapper on Cloud Run with Firebase Hosting
What is the challenge here?
In the previous post, the Sapper application’s
static directory is hosted within the Docker image. When a user visits the web application and requests the
/favicon.ico file for example, that request goes to Cloud Run and the server responds with the icon.
The Cloud Run service we created in the previous post is hosted in the us-central1 region. When a user from South Korea visits the web application, her request travels all the way across the Pacific Ocean, half way through the United States until it reaches the Cloud Run service. The response then travels back the same way - that’s a lot of traveling… While a lot of that journey happens on Google Cloud’s global fiber network, it still adds latency. Check out this interactive explorer of what I’m talking about.
No problem, deploy the service in Korea
Not a bad idea, it certainly solves the latency for Koreans. It’s not a globally scalable solution though as we would have to continuously deploy to new regions as and when they become available.
A content delivery network (CDN) to the rescue
With a CDN, we can distribute the static assets to many locations around the world with minimal effort. The best part: We don’t have to deal with the complexity of all that as we will see in a minute.
Create a Firebase project
To stick to the cloud provider choice we made in the previous post, we are going to use Firebase and it’s Firebase Hosting solution.
As these instructions may change, please follow the first two steps of the documentation:
- Step 1: Create a Firebase project
Note: Use the existing GCP project from the previous blog post. Do not create a new GCP project!
- Step 2: Register your app with Firebase
Note: Make sure you follow the (optional) instructions to set up Firebase Hosting for our web app.
Set up Firebase Hosting (Pull Request)
Anything discussed going forward assumes you read the previous blog post and have a repository set up accordingly.
To interact with the Firebase web application you created in the previous chapter, we use the Firebase command line interface (CLI).
To keep the Firebase scripts and configuration files out of the main Sapper web app directory, let’s create a
firebase folder at the root of the project. We also need a
package.json file where we can add NPM scripts to deal with Firebase:
cd $_ # The same as `cd firebase`, but less to type
Let’s also generate a
package.json file within that directory. This allows us to write reusable NPM scripts and simplify the process later.
Follow the prompts and provide meaningful values.
Still in the
firebase directory, install the Firebase CLI with
npm install -D firebase-tools.
Install firebase dependencies automatically
Separating the Firebase project files into their own directory keeps the code clean and maintainable. It does come with one drawback which we can easily mitigate. The challenge is that the
firebase-tools dependency doesn’t get automatically installed when the Sapper app dependencies are installed.
Ideally, once the dependencies for the Sapper app are installed, we would like to also install the dependencies defined in the
We can do this with a
postinstall script in the root’s
package.json file. This is automatically executed by NPM after the
npm install command completes.
package.json, add the following script:
"postinstall": "npm install --prefix firebase",
Note: You can use
pre* scripts for any defined NPM script, including your own custom scripts.
Log in to Firebase
Before we can initialize the Firebase project, we need to authenticate. To simplify that for other team members, let’s add the following NPM script to
"firebase:login": "firebase login",
If you use a cloud-based development environment such as Gitpod.io, you can use the
--no-localhost argument, as in
firebase login --no-localhost. Then follow the instructions on the screen when you run the
Let’s log in. Run
npm run firebase:login in your terminal and follow the instructions. Once completed, you are now successfully logged in to Firebase and we can initialize the project.
Initialize Firebase CLI
To initialize the Firebase app, run the following command:
There’s no need to create a NPM script similarly to how we did that for the login command. We only run the init command once.
init command provides an interactive interface to configure the Firebase CLI. When you’re asked which Firebase CLI features you want to set up, select the following:
- Hosting: Configure and deploy Firebase Hosting sites
Press Enter to confirm your selection.
Select Use an existing project for the next question, then choose the project we created in the Firebase web console earlier and confirm with Enter. If you’re new to Firebase, you only see one option.
Keep all default values and say “N” when you’re asked whether to configure as a single-page app.
With that, the one-time initialization of the Firebase SDK is complete.
Deploy to Firebase Hosting
In its essence, deploying to Firebase Hosting is a single command:
package.json (the one in the
firebase directory) and add the following script:
"deploy": "firebase deploy",
We can test this by running
npm run deploy in the
When completed, the command displays a Hosting URL. Open this in your browser to see a placeholder page. This URL is where the web application will appear once we finish the automated deployment pipeline!
The content you currently see is defined in the
public/index.html file. The deploy process publishes all files in the
public directory. In our case, we want to copy the static files of the Sapper web application to that
public directory. Given that, let’s delete the
public directory as it will be generated automatically in the next chapter. We also want to tell git to ignore it by adding
public to the
To ensure the static content is copied every time we deploy to Firebase, we can leverage a feature of Firebase Hosting called predeploy scripts. Open
firebase/firebase.json and add the following to the
"predeploy": "rm -fr ./public && cp -r ../static ./public"
Lastly, we need to direct hosting requests to the containerized app that runs on Cloud Run. To achieve that, we again open
firebase/firebase.json and extend the
An extra 41 lines of code, including some NPM scripts for convenience, we managed to improve the end user experience for a Sapper application hosted on Cloud Run quite a bit. All static assets are now served through a CDN provided by Firebase Hosting. With that, these assets are distributed to edge locations around the world and served to end users with minimal latency.
To compare, here are the WebPageTest results without Firebase Hosting (i.e. what we developed in the previous blog post):
And here the WebPageTest results with Firebase Hosting (i.e. what we developed in this blog post):