Using Cache-Control and CDNs to Improve Performance and Reduce Latency
Tags: webdev, tutorial, performance, productivity
Published: 2021-05-21 11:34:29

We believe latency and caching are crucial subjects we need to talk about. Loading times are a critical component in the user's experience for all apps and
websites: **new pages and screens should load in less than a second** for
end-users to have a smooth experience.

At Koyeb, we provide a global edge network with a built-in CDN to help you
achieve that with *caching* and *TLS termination at the edge*, but there is an important topic which is always your
responsibility when you use a CDN: `Cache-Control`
HTTP headers.

In this post, we review how caching works and dive into `Cache-Control` HTTP headers, which are
used to configure how your content is cached across the internet and end-user
devices. We will explore how to configure them and what guidelines to follow
when using them.

Getting caching right can be tricky and getting it wrong is headache-inducing.
Learning about it is even more critical now that over 50% of the internet's traffic is
served towards mobile devices where interactions are highly latency-sensitive
and long loading times will trigger high bounce rates.

**Table of Contents**

* [Caching Types, Benefits, and Overview](#caching-types-benefits-and-overview)
* [The Cache-Control Directives](#the-cache-control-directives)
* [Cache Invalidation](#cache-invalidation)
* [Configuring Cache-Control for your Sites and Resources](#configuring-cache-control-for-your-sites-and-resources)

## Caching Types, Benefits, and Overview

Caching technologies temporarily store data between origin servers and
end-users to improve site performance and reduce latency for the requests of your web services and apps.

At a high level, `Cache-Control` is an HTTP header that specifies caching policies for client requests and server responses. These policies, which are configured parameters, determine how resources are cached, where, and their time-to-live before expiring. 

- **Cacheable** refers to whether the content can be stored in cached;
- **Where** is important given cache can exist locally in a browser or in a server;
- and **time-to-live** is the amount of time content is considered fresh.

### Types of Web Caches

It is important to understand there are 3 main locations where content is cached:

1. **Gateway Caches** - They are a component of your infrastructure designed to
increase your application performance. Koyeb provides Gateway Caches, also called **Reverse
Proxy Cache**, in 55 locations across the globe as part of its edge network.
2. **Proxy Caches** - Deployed by businesses or internet service providers
(ISPs) to create a type of shared cache. These caches reduce latency and network
traffic to your origin server but you have no direct control over them. HTTP
Headers are critical to indicating what they can cache.
3. **Browser Caches** - Use a part of the hard drive or memory of the end-user
device to store a copy according to the application's cache settings.

### Cache Benefits

There are 3 key benefits to caching: 

1. **Reduce response time**: Caching improves response time by storing rendered pages and
assets in a location nearer to your end-users. When content is served from a
cache, it removes the compute time needed to render
the page and reduces the network round
trip time needed to hit the server.
2. **Reduce load on your application**: By serving cached assets directly from the
CDN or the browser, you reduce the number of requests that hit your origin
servers and infrastructure.
3. **Reduce bandwidth costs**: Important if your website generates a lot of traffic.

### Broad Overview of How Caching Works

![cache schema](https://www.koyeb.com/static/images/blog/cache-flow.svg)


## The Cache-Control Directives

### HTTP 1.1 improved caching possibilities with Cache-Control

Before diving into how to use `Cache-Control`, it is helpful to review briefly the history of configuring cache.

#### HTTP 1.0 Caching Limitations

With HTTP 1.0, caching headers were limited to:

- `Expires:` This is an age limitation for cached content, and it is configured to a specific date and time. It is an absolute limit that is not relative to the client's request.

```jsx
Expires: Mon, 22 Apr 2021 23:59:25 GMT
```

- `Pragma: no-cache` This is a no-cache meta tag that does not necessarily keep a web page fresh.

#### HTTP 1.1 New Caching Headers

The release of HTTP 1.1 addressed the limitations of caching with `Expires:` and `Pragma: no-cache` by adding a new class of headers, `Cache-Control`.  These new headers offer more control over their content than previously possible with HTTP 1.0.

Depending on your needs, you can use multiple `Cache-Control` directives by separating each of them with a comma. 

```jsx
Cache-Control: public, max-age=60, stale-while-revalidate=180

```

Below is a non-exhaustive list of the different request and response directives you can use for `Cache-Control`. 

### Request Directives for Cache-Control

**Expiration**

`max-age` - The maximum number of seconds a representation is considered fresh. This time limit is relative to the client's request whereas the time limit of `Expires` is absolute. If both headers are used, `max-age` takes priority over `Expires`.

```jsx
Cache-Control: max-age=3600
```

_Note: `max-age` is an improvement from `expires` given that it is a relative time-to-live for each request. The relativity of the `max-age` directive mitigates for unintentional DDoS or spikes in traffic._

`max-stale` - The maximum number of seconds a client will accept a stale response. *A stale response is a response that is older than the time specified by `max-age`.*

`min-fresh` - The minimum number of seconds a client wants a response to be fresh. *A cache is fresh as long as the amount of time of max-age has not expired.*

`no-cache` - The stored response must be validated with the origin server before being used. This is a good option if important information on your site changes, but you still want the benefits of caching. 

`no-store` - Blocks a cached copy from being stored anywhere, be it in a local cache, a browser, or third-party proxy. This directive forces each request to travel to the origin server to access the content. It is expensive and slower, but it does guarantee up-to-date content. _Important note: this directive cannot be used with other directives._ 

**Extension**

`stale-if-error`- If there is an error during a check for a fresh response, this is the number of seconds the client will accept a stale response. If the number of seconds has not been surpassed, `stale-if-error` returns a cached response.

### Response Directives for Cache-Control

**Caching**

`private` - The response can only be stored on a browser's response. Use this directive when the content is meant for a specific end-user. For example, an individual email account.

`public` - The response can be stored on any cache. Use this directive when anyone can see the content. For example, a product's pricing page.

`no-cache` - The stored response must be validated with the origin server before being used. This is a good option if important information on your site is updated, but you still want the benefits of caching. 

`no-store` - Blocks a cached copy from being stored anywhere, be it in a local cache, a browser, or third-party proxy. This directive cannot be used with other directives.

**Expiration**

`max-age` - The number of seconds after the initial request that a representation is considered fresh. This is relative compared to `Expires`, which is absolute. `max-age` takes priority over `Expires`.

_Note: `max-age` is an improvement from `expires` given that it is a relative time for each request rather than an absolute expiration date shared across clients. The `max-age` directive mitigates for unintentional DDoS or spikes in traffic and is indifferent towards the clocks or timezones for each client._

`s-maxage` - Like `max-age`, but only for shared proxies, like CDN providers.

`must-revalidate` - Forces request to validate with the origin server.

`proxy-revalidate` - Like `must-validate` but only for proxy caches, not private caches.

**Extension**

 `stale-while-revalidate` - Used with `max-age`, it is the number of seconds a client will accept a stale response. 

When a client request comes in for content, `max-age` is used to determine if the response can be cached content. If the `max-age` time has expired, `stale-while-revalidate` is used to determine if the request will accept *stale* content.

This directive strikes a balance between loading cached content quickly and ensuring fresh content. It is most often used with asynchronous background workers to check for fresh content.

_Important note: you can only use this cache-control header with Chrome and Firefox. Other browsers will ignore this header and only use `max-age`._

`stale-if-error`- Returns a cached response if an error is encountered, regardless of the content's freshness.

### Validators

Validators check changes between the cached copies and the files at the origin server. If the content is valid, the client's request does not need to be sent all the way to the origin server.

`Last-Modified` - Sent by the origin server in its response, `Last-Modified` marks the last modification to the resource. This validator is often used alongside the `Expires` header.

```jsx
Last-Modified: Fri, 8 Jan 2021 11:32:14 GMT
```

`If-Modified-Since` - This is a conditional validator that is sent by the client to check when the file was last changed to determine if the cached content matches what exists at the origin server. If the cached content matches what exists at the origin server, the origin server returns a 304 to say it is ok to return the cached copy.

In addition to `Cache-Control` headers, HTTP 1.1 also introduced a new validator: 

`ETag` - The entity tag hashes a file byte by byte to create a digital fingerprint of the file. These unique identifiers determine if the cached content in a browser matches the content at the origin server.

Once a response, be it an ETag or conditional validator, is validated with the origin server, the origin server returns a `304 Not Modified` response to the client. See [illustration](#broad-overview-of-how-caching-works) for more.

## Cache Invalidation

Cache invalidation is a way to remove or update an object from the cache before its scheduled expiration time. It is usually reserved for special circumstances, as the best practice remains to configure an appropriate expiration time for your cacheable content with `s-maxage`. 
There are 

Higher `private` time limits make it difficult to update content cached in local browsers Google provides a comprehensive [cache invalidation overview](https://cloud.google.com/cdn/docs/cache-invalidation-overview). 

## Configuring Cache-Control for your sites and resources

There are lots of options for configuring a `Cache-Control` header. While there is not necessarily a standard, there are best practices that can guide you when configuring `Cache-Control` for your websites.

![Tree Diagram Cache-Control](https://www.koyeb.com/static/images/blog/cache-control-tree-diagram.png)

### Cache Use Cases: A matter of how often you update your resources

You'll want to configure your `Cache-Control` duration parameters depending on how frequently data is updated on the origin server. For resources that do not change so often, you could set the `max-age` directive to 86400 seconds (equal to 24 hours) or even 31536000 seconds (equal to 1 year). In any case, another practice you'll want to keep in mind is keeping `max-age` for `private` and browser caches short while setting larger time-to-live values for `s-maxage` and `public`. Again, higher `private` time limits make it difficult to update content cached in local browsers.

Below is a table we've prepared outlining best practices for real-world examples.

## Cache-Control Use Case Examples

|Use Case|Best Cache Practice|Example Cache-Control|
|:---|:---|:---|
|Static assets like images or fonts.|Public cache control to cache the information on proxy servers.|Cache-Control: public, max-age=31536000, immutable|
|Resources such as documents, images that are only available for one particular user or for authorized users.|Cache on browsers, not on proxy servers.|Cache-Control: private, max-age=3600|
|Content that changes, but not rapidly, and where multiple users will request the same content i.e., apps for news headlines or weather conditions.|Public cache to proxy servers with the possibility to fetch stale content.|Cache-Control: max-age=360, stale-while-revalidate=1200|
|Prevent returning errors if the origin server is unreachable when cache needs to be revalidated.|Use stale-if-error to continue serving the page resources beyond the time expiration of the resource.|Cache-Control: public, max-age=3600, stale-if-error=900|
|Dynamic content on social media feeds.|Never cache this data.|Cache-Control: no-store|
|Content that should be revalidated before being rendered.|Guarantee the freshest content without sending a new response every time.|Cache-Control: public, no-cache|
|Cached resources on your application deployed on Koyeb that require to be invalidated when a new version is deployed.|Cache at the edge as Koyeb invalidates the cache each time a new deployment occurs.|Cache-Control: s-maxage: 2592000|

### Caching with CDNs increases performance

Since CDNs store cached copies of your site and app in their network of data centers and proxy servers, CDNs shorten the distance between your users around the world and the server where your site or app is hosted on. 

In addition to improving the latency experienced by your end-users, CDNs increase the performance of your sites and apps because gateway caching reduces the traffic that reaches the origin server. 

### Global load-balancing and built-in CDN with the Koyeb Serverless Platform

All serverless deployments on Koyeb benefit from the platform's native global load-balancing and CDN. Traffic is routed through the nearest edge location for the end-user to reduce delivery latency.

Koyeb is a developer-friendly serverless platform that hosts web apps and services, Docker containers, APIs, event-driven functions, cron jobs, and more!

Thanks to native support of popular languages and built-in Docker container deployment, you can use Koyeb's serverless platform to deploy your projects.

With Koyeb, you can scale like internet giants without their budget. See the [benefits of going serverless](/www.koyeb.com/blog/going-serverless-implications-benefits-and-challenges), get started by [signing up](//app.koyeb.com/auth/signin) today and joining us on [Slack](//slack.koyeb.com/).