Firebase hosting and Cloud Run cache

Neil Kolban
Google Cloud - Community
4 min readJul 7, 2020

We can use Firebase hosting to host a web-site. Within the deployed site, we can specify that some or all desired URL paths can be directed to Cloud Run for processing. This allows us to generate dynamic server-side rendered content through applications running in containers. Firebase hosting is also intrinsically linked with Google’s Content Delivery Network (CDN) to provide efficient edge caching. When a browser request arrives at Google, the CDN is consulted to determine if the content can be served from the edges. If not available in cache, the request is then routed to Firebase for serving. The use of CDN can dramatically decrease latency and improve overall performance.

In addition to static content, server-side generated content is also cached by default. This may be exactly what you want but it also may not be desirable. Imagine a simple server side rendered page that generates content with the current date/time.

It is now 10:51pm.

In the server side code, this may be generated using:

res.send(`It is now ${new Date().toString()}`);

The first time a browser requests the page, it won’t be found in CDN and the Cloud Run code executed to build the HTML. The content will then be cached in CDN and subsequent calls will return it from the cache. This means that the data will not change and we will be stuck at that single response and the time will not appear to update.

  1. The browser request arrives at CDN and there is nothing cached. The request is then sent to Firebase Hosting that routes to Cloud Run for content generation.
  2. The response is written to CDN for caching.
  3. A later browser request for the same URL is served directly from CDN. No request is made to Cloud Run.

We have a powerful technique at our disposal to control how caching is managed. When the HTTP response from Cloud Run is returned it can include additional HTTP headers. One such header is called “Cache-Control”. The returned values are honored by both the browser and Google CDN. You should consult the documentation on Cache-Control for full details. Some values that we can use to configure the response interaction with CDN include:

Cache-Control: no-store

This declaration says that the data should not be cached. Specifically, when it is returned from Cloud Run it will not be cached by CDN. This means that every request for the Cloud Run generated data will result in Cloud Run logic being re-executed to build a new and current response.

If we do wish to leverage CDN caching, we can specify:

Cache-Control: public,s-maxage=30

The “s-maxage” property defines a duration in seconds for which the cached value should be stored. After this interval has elapsed, the cached value will be discarded.

This HTTP header can be returned from the Cloud Run container implementation that you implemented. Since you can implement these containers in any language, you choose the recipe to explicitly set the header in the implementation is language specific. For example, in NodeJS we might code:

res.set('Cache-Control', 'no-store');
res.send('… My generated HTML …');

This explicit coding recipe is not the only way we can control CDN and Cloud Run interactions. When we deploy a solution to Firebase hosting, we supply a configuration file called firebase.json. One of the features of Firebase hosting is the ability to inject headers in the incoming request or outgoing response based on URL pattern matching. Using this feature, we can specify the value of the “Cache-Control” HTTP header returned from a Cloud Run container call without having to explicitly code the Cache-Control in our implementation logic.

Let us imagine that we have a URL Path called “/mycontent”. Let us now assume that we want this page to be server-side rendered by Cloud Run and not-cached. How might we do this?

We could implement a NodeJS application that listens for this URL:

app.get('/mycontent', (req, res) => {
res.send(`
<html>
<head>
</head>
<body>
<p>Hello from Cloud Run (/mycontent): ${new Date().toString()}</p>
</body>
</html>
`);
});

Notice that it does not contain any explicit caching control. When we deploy this to Cloud Run, we give it a logical service name. In our example, we’ll call it “mycontent”. Within the Firebase hosting firebase.json configuration file, we can now provide a mapping from the desired path to the Cloud Run service. We would also specify the “Cache-Control” header to be returned:

{
"hosting": {
"rewrites": [{
"source": "/mycontent",
"run": {
"serviceId": "mycontent",
"region": "us-central1"
}
}]
"headers": [{
"source": "/mycontent",
"headers": [{
"key": "Cache-Control",
"value": "no-store"
}]
}]
}
}

The way to interpret the above is that for requests arriving that are to be served from “/mycontent”, pass the request to the Cloud Run service called “mycontent” deployed in “us-central1”. In addition, for any response that comes from the logical path “/mycontent”, set the “Cache-Control” header to have a value of “no-store”.

A video illustrating more of this story can be found here:

References:

--

--

Neil Kolban
Google Cloud - Community

IT specialist with 30+ years industry experience. I am also a Google Customer Engineer assisting users to get the most out of Google Cloud Platform.