Service Worker Caching and HTTP Caching
Tags: serviceworker, pwa, caching, javascript
Published: 2020-04-26 09:37:44

# TL;DR

This article covers use cases of Service Worker Caching and HTTP Caching, with suggestions of setting expiry for both caching layers. Here are the highlights:

*   Service Worker Caching and HTTP Caching serve different purposes and use cases.
*   Service Worker Caching doesn’t need to be consistent with HTTP Caching expiry.
*   HTTP Caching still plays an important role in the cache layers, but it’s not reliable when the network is not stable or down.
*   Revisit the caching strategies for each resource to make sure Service Worker Caching strategy provides its value, without the conflict with HTTP Caching.

# Table of Contents
1. [Context and Background](#context-and-background)
2. [Caching Flow](#caching-flow)
3. [Caching Layers](#caching-layers)
  - [Service Worker Caching](#service-worker-caching)
  - [HTTP Caching (a.k.a. Browser Cache)](#http-caching-a-k-a-browser-cache)
4. [Cache Expiry](#cache-expiry)
  - [Consistent Cache Expiry for All Cache Layer](#consistent-cache-expiry-for-all-cache-layer)
  - [Different Cache Expiry for each Cache Layer](#different-cache-expiry-for-each-cache-layer)
5. [Conclusion](#conclusion)

# Context and Background

While Service Worker and PWA are becoming a standard of modern web applications, resource caching has become more complex than ever. To address the big picture of browser caching, this article covers the following:

*   A thorough explanation of Service Worker caching vs. HTTP caching: use cases, and the difference.
*   The pros and cons of having different Service Worker caching expiry compared to regular HTTP caching.

# Caching Flow

In a high-level view, a browser follows the caching order below when it requests for a resource:

1.  **Service Worker Cache:** The Service Worker will check if the resource is in its Cache (either Cache Storage API or IndexedDB) based on the Service Worker’s caching strategies, and decides whether to return the resource directly. HTTP Cache will not be checked at this point.
1.  **HTTP Cache (a.k.a. Browser Cache):** if the resource is found in the HTTP Cache and not expired yet, the browser will use the resource from the  HTTP Cache.
1.  **CDN / Origin Server:** If nothing is found in the Service Worker Cache or HTTP Cache, the request will be passed to the Server (mostly CDN) to fetch the latest resource.


![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/g6j0323d874a10mahb36.png)


Please note that some browsers like Chrome have a **Memory Cache** layer in front of the Service Worker Caching. The detail of Memory Cache depends on each browser’ implementation. Unfortunately, there is no clear specification for this part yet. This section will be updated once there is more clarification from browsers’ specification.


# Caching Layers

## Service Worker Caching

A Service Worker intercepts Network-type HTTP requests and has full control of what to respond back to the browser based on [its caching strategies](https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook#serving-suggestions). Although in most cases Service Worker caching may cover the similar scope compared to HTTP Caching, Service workers offer additional caching capabilities with fine-grained control over exactly what and how caching is done for faster or even offline web experiences.

### Control Service Worker Cache

A Service Worker intercepts HTTP requests with [Event Listeners](https://github.com/mdn/sw-test/blob/gh-pages/sw.js#L19) (E.g. fetch event). The logic of caching is implemented (and customized) inside the Service worker. This [code snippet](https://github.com/mdn/sw-test/blob/gh-pages/sw.js#L19) implemented the logic of Cache-First caching strategy.


![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/idaeq6f6twal312j4vxp.png)


It’s highly recommended to use [Workbox](https://developers.google.com/web/tools/workbox) without the need of reinventing the wheel. For example, you can [register resource URL paths with a single line of code using regex](https://developers.google.com/web/tools/workbox/modules/workbox-routing#how_to_register_a_regular_expression_route).


```javascript
import {registerRoute} from 'workbox-routing';

registerRoute(
  new RegExp('/styles/.*\\.css'),
  handlerCb
);
```


In addition to the caching strategies, you can also implement the logic of cache expiry (TTL) for each resource in Service Worker cache. If you use [Workbox](https://developers.google.com/web/tools/workbox) for Service Worker implementation, you can directly [apply it with the Cache Expiration plugin](https://developers.google.com/web/tools/workbox/modules/workbox-expiration).


### Service Worker Caching Strategies and Use Cases

Depending on the type of resources, Service Worker controls how and when to serve cached content or to pass the HTTP requests to remote servers to get new content. The following are the most common caching strategies and their use cases:

<table>
  <tr>
   <th width="30%"><strong>Strategies</strong>
   </th>
   <th width="40%"><strong>Freshness Rationale</strong>
   </th>
   <th><strong>Use Cases</strong>
   </th>
  </tr>
  <tr>
   <td><strong>Network Only</strong>
   </td>
   <td>The content has to be up-to-date at all times.
   </td>
   <td><ul>

<li>Payment and checkout
<li>Balance statement</li></ul>

   </td>
  </tr>
  <tr>
   <td><strong>Network first, then cache </strong>
   </td>
   <td>It’s critical to serve the fresh content. However if the network fails or is unstable, it’s torrelable to serve slightly old ones.
   </td>
   <td><ul>

<li>Timely data
<li>Price, rates. (Requires disclaimers)
<li>Order status</li></ul>

   </td>
  </tr>
  <tr>
   <td><strong>Stale-While-Revalidate</strong>
   </td>
   <td>It’s okay to serve a slightly older content, but still keep the high freshness of the content.
   </td>
   <td><ul>

<li>News feed
<li>Product listing page
<li>Messages</li></ul>

   </td>
  </tr>
  <tr>
   <td><strong>Cache First, </strong>
<p>
<strong>fall back to network</strong>
   </td>
   <td>Serving non-critical content from cache to users for gaining performance, but will try to update these content occasionally.
   </td>
   <td><ul>

<li>App shell
<li>Common resources</li></ul>

   </td>
  </tr>
  <tr>
   <td><strong>Cache Only</strong>
   </td>
   <td>The content rarely changes.
   </td>
   <td><ul>

<li>Static content</li></ul>

   </td>
  </tr>
</table>



### Additional benefits of Service Worker Caching

In addition to the complete-control of caching with Service Worker, there are a few more benefits to add:

*   **Reserved memory space for your own origin:** HTTP Cache shares memory/disk for all domains. There is no guarantee for the developer that content of your origin/domain stays in HTTP Cache for a long time. With Service Worker Cache you have a MUCH higher likelihood that your cached content keeps cached. (https://developers.google.com/web/updates/2016/06/persistent-storage).
*   **Higher flexibility with flaky networks or offline experience:** With HTTP Cache you only have a binary choice. Either the resource is cached, or not. If you have a flaky network, then you are either lucky or not. With Service Worker Caching you can mitigate little "hiccups" much easier (with StaleWhileRevalidate strategy), offer complete offline experience (with CacheOnly strategy) or even something in between, like customized UIs with parts of the page coming from the Cache and some parts excluded/hidden (with SetCatchHandler strategy) where appropriate.


## HTTP Caching (a.k.a. Browser Cache)

The first time a browser loads a web page and related resources, it stores these resources in the HTTP Cache. This is usually enabled automatically by browsers, unless the caching function is explicitly disabled.


### Control HTTP Cache Expiry with HTTP headers

HTTP Caching expiry is mostly controlled with HTTP headers:



*   **Cache-Control**: `max-age` specifies a relative amount of time for validating a cached resource, as TTL (Time-To-Live). For example, `max-age=60` means this cached resource is valid for only 1 minutes. `max-age=0` means no cache at all for a resource. Each resource can define its caching policy. ([More details](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching#cache-control)).
*   **Expires: `expires`** sets a specific date when a cached resource needs to refresh. In most cases, `expires` is recommended for static resources like images.
*   **ETag**: This is additional information stored with a cached resource, mostly used for cache busting at the server side.

In most cases, Cache-Control and Expires accomplish the same thing. The browser will fetch the latest resource from remote servers when either condition meets. ETag is mostly used for cache-busting for the server-side.

Using HTTP caching means it’s relying on the server to determine when to cache a resource and when it expires.


### HTTP Caching Strategies and Use Cases

The scenario of HTTP Caching is much simpler than the Service Worker caching, since HTTP Caching only works with the TTL concept.


<table>
  <tr>
   <th><strong>TTL Strategies </strong>
   </th>
   <th><strong>Freshness Rationale</strong>
   </th>
   <th><strong>Use Cases</strong>
   </th>
  </tr>
  <tr>
   <td width="30%"><strong>No Cache</strong>
   </td>
   <td width="40%">The content has to be up-to-date at all times.
   </td>
   <td><ul>

<li>HTML documents
<li>Timely data
<li>Price, rates.
<li>Order status
<li>Messages</li></ul>

   </td>
  </tr>
  <tr>
   <td><strong>Short term (a few seconds to 1 day)</strong>
   </td>
   <td>It’s okay to serve a slightly older content, but still keep the high freshness of the content at least every day.
   </td>
   <td><ul>

<li>News feed
<li>Product listing page</li></ul>

   </td>
  </tr>
  <tr>
   <td><strong>Mid-term (1 day to 1 month)</strong>
   </td>
   <td>It’s okay to serve older content, and use Cache Busting to refresh content when needed.
   </td>
   <td><ul>

<li>App shell
<li>Common resources</li></ul>

   </td>
  </tr>
  <tr>
   <td><strong>Long-term (> 1 month)</strong>
   </td>
   <td>The content rarely changes. Use Cache Busting to refresh the content when needed.
   </td>
   <td><ul>

<li>Static content</li></ul>

   </td>
  </tr>
</table>



# Cache Expiry

TL;DR

*   Service Worker Caching and HTTP Caching serve different purposes and use cases. (See previous sections for details)
*   There are some downsides when setting Service Worker and HTTP Caching with the same expiry. For example: HTTP Caching may provide less value. It may require additional cache-busting in Service Worker.
*   One technique to make the most of Service Worker Caching is to set shorter HTTP Caching expiry for resources.

Designing the caching expiry policies is not an easy task, especially when dealing with multiple caching layers. Given that Service Worker Caching and HTTP Caching serve different purposes with different logic, it is not necessary to set with the same expiry for both caching layers. This section covers the pros and cons of whether to set consistent or separate cache expiry for cache layers.

You can [make a copy of this glitch demo](https://glitch.com/edit/#!/remix/sw-caching-flow) to see how Service Worker Caching and HTTP Caching work in actions with various scenarios.


![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/eneg8h4dgtgcsxw881k7.png)



## Consistent Cache Expiry for All Cache Layer

We will look into just three scenarios: Long-term, mid-term and short-term caching scenarios to present the pros and cons of setting cache expiry.


<table>
  <tr>
   <th>Scenarios
   </th>
   <th>Long-term caching
   </th>
   <th>Mid-term caching
   </th>
   <th>Short-term caching
   </th>
  </tr>
  <tr>
   <td>Service Worker Caching Strategy
   </td>
   <td>Cache First, fall back to network.
   </td>
   <td>Stale-While-Revalidate
   </td>
   <td>Network first, then cache
   </td>
  </tr>
  <tr>
   <td>Service Worker Cache TTL
   </td>
   <td><strong>TTL=30 days</strong>
   </td>
   <td><strong>TTL=1 day</strong>
   </td>
   <td><strong>TTL=10 mins</strong>
   </td>
  </tr>
  <tr>
   <td>HTTP Caching
   </td>
   <td>max-age=<strong>30 days</strong>
   </td>
   <td>max-age=<strong>1 day</strong>
   </td>
   <td>max-age=<strong>10 mins</strong>
   </td>
  </tr>
</table>


Let’s look at each scenario in detail:


### Scenario: Long-term caching



*   **When a cached resource is valid (<= 30 days)**: Service Worker returns the cached resource immediately without checking HTTP Caching.
*   **When a cached resource is expired (> 30 days)**: Service Worker passes the request to HTTP Caching, which directly passes the request to the remote servers.

Pros and Cons:



*   **Con:** In this scenario, HTTP Caching provides less value because it will always pass the request to remote servers directly when the cache expires in Service Worker.


### Scenario: Mid-term caching



*   **When a cached resource is valid (<= 1 day)**: Service Worker returns the cached resource immediately, and checks with HTTP Caching for a new version, which returns the resource from HTTP Cache.
*   **When a cached resource is expired (> 1 day)**: Service Worker returns the cached resource immediately, and checks with HTTP Caching for a new version, which pass the requests to remote servers.

Pros and Cons:

*   **Con:** Service Worker requires additional cache-busting to override HTTP Cache in order to make the most of the “Revalidate” step.


### Scenario: Short-term caching



*   **When a cached resource is valid (<= 10 mins)**: Service Worker checks with HTTP Caching for a new version of resource, which returns the resource from HTTP Cache without checking remote servers.
*   **When a cached resource is expired (> 10 mins)**: Service Worker checks with HTTP Caching for a new version of resource, which passes the requests directly to remote servers.

Pros and Cons:

* **Con:** Similar to the mid-term caching, Service Worker requires additional cache-busting to override HTTP Cache in order to fetch the latest resource from remote servers.


### Service Worker in all scenarios

In all scenarios, Service Worker can still return cached resources when the network is unstable. On the other hand, HTTP Caching is not reliable when the network is unstable or down.


## Different Cache Expiry for each Cache Layer

On the other hand, we’ll take a look into the pros and cons of using different cache expiry for Service Worker Caching and HTTP Caching.


<table>
  <tr>
   <th>Scenarios
   </th>
   <th>Long-term caching
   </th>
   <th>Mid-term caching
   </th>
   <th>Short-term caching
   </th>
  </tr>
  <tr>
   <td>Service Worker Caching Strategy
   </td>
   <td>Cache First, fall back to network.
   </td>
   <td>Stale-While-Revalidate
   </td>
   <td>Network first, then cache
   </td>
  </tr>
  <tr>
   <td>Service Worker Cache TTL
   </td>
   <td><strong>TTL=90 days</strong>
   </td>
   <td><strong>TTL=30 days</strong>
   </td>
   <td><strong>TTL=1 day</strong>
   </td>
  </tr>
  <tr>
   <td>HTTP Caching
   </td>
   <td>max-age=<strong>30 days</strong>
   </td>
   <td>max-age=<strong>1 day</strong>
   </td>
   <td>max-age=<strong>10 mins</strong>
   </td>
  </tr>
</table>


Let’s look at each scenario in detail:


### Scenario: Long-term caching (Cache First, fall back to network)

*   **When a cached resource is valid in Service Worker Cache (<= 90 days)**: Service Worker returns the cached resource immediately without checking HTTP Caching.
*   **When a cached resource is expired in Service Worker Cache (> 90 days)**: Service Worker pass the request to HTTP Caching, which passes the request to remote servers.

Pros and Cons:



*   **Pro**: Users experience instant response as Service Worker returns cached resources immediately.
*   **Pro**: Service Worker has more fine-grained control of when to use cache and when to request for new version of resources.
*   **Con**: A well-defined Service Worker caching strategy is required.


### Scenario: Mid-term caching (Stale-While-Revalidate)

*   **When a cached resource is valid in Service Worker Cache (<= 30 days)**: Service Worker returns the cached resource immediately without checking HTTP Caching.
*   **When a cached resource is expired in Service Worker Cache(> 30 days)**: Service Worker passes the request to HTTP Caching. HTTP Caching passes the request to remote servers, or may return from HTTP Cache if it’s cached version is still valid due to the difference of resources’ last modified time.

Pros and Cons:

*   **Pro**: Users experience instant response as Service Worker returns cached resources immediately.
*   **Pro**: Service Worker has more fine-grained control of when to use cache and when to request for new version of resources.
*   **Con**: A well-defined Service Worker caching strategy is required.


### Scenario: Short-term caching (Network first, then cache)



*   **When a cached resource is valid in Service Worker Cache (<= 1 day)**: Service Workers checks HTTP Caching first. If the network is down, Service Worker returns the cached resource.
*   **When a cached resource is expired in Service Worker Cache(> 1 day)**: Service Worker pass the request to HTTP Caching, which may return from HTTP Cache if it’s cached version is still valid, or pass the request to remote servers.

Pros and Cons:

*   **Pro**: When the network is unstable or down, Service Worker returns cached resources immediately.
*   **Con:** Service Worker requires additional cache-busting to override HTTP Cache in order to make “Network first” requests.


# Conclusion

Given the complexity of the combination of caching scenarios, it’s not possible to conclude one rule that covers all cases. However, based on the findings in the previous sections, there are a few suggestions to look at when designing with cache:



*   Service Worker Caching doesn’t need to be consistent with HTTP Caching expiry. If possible, set longer Service Worker Caching expiry compared to HTTP Caching expiry to grant more control in Service Worker.
*   HTTP Caching still plays an important role in the cache layers, but it’s not reliable when the network is not stable or down.
*   Revisit the caching strategies for each resource to make sure Service Worker Caching strategy provides its value, without the conflict with HTTP Caching.

If you would like to learn more about Service Worker and its caching strategies, please refer the following resources for more information:



*   [Network reliability on web.dev](https://web.dev/reliable/)
*   [HTTP Caching basics](https://developers.google.com/web/fundamentals/performance/get-started/httpcaching-6)
*   [HTTP Cache Codelab](https://web.dev/codelab-http-cache/)
*   [Measuring the Real-world Performance Impact of Service Workers](https://developers.google.com/web/showcase/2016/service-worker-perf)
*   [Cache-Control vs. Expires](https://stackoverflow.com/questions/5799906/what-s-the-difference-between-expires-and-cache-control-headers)