Front-End: Cache Strategies You Should Know

Emma Delaney
8 min readDec 11, 2023
Front-End: Cache Strategies You Should Know

There are several headers that developers and ops can use to manipulate cache behavior.

The old specification is mixed with the new: it needs many settings to configure and multiple users may report inconsistent behavior.

In this article I will focus on explaining how different headers affect browser cache and about how they are related. for proxy servers.

You can find configuration examples for Nginx and code for Node.js running Express. Finally, we’ll look at how popular services built into React serve your web applications.

For a single-page application, I’m interested in JavaScript, l The implementation caches CSS, fonts, and image files indefinitely, and prevents caching of HTML files and service workers (if applicable).

This strategy is viable as a resource file. They have unique identifiers in the file names.

You can do the same configuration in WebPack to include a [hash] or, even better, a [chunkhash]. in the file name of your assets. This technique is called long-term caching.

But how can you update your website if it prevents new downloads? To maintain the site’s ability to update, it is very important to never cache HTML files.

Every time you visit my site, your browser displays a new copy of the HTML from the server and only if there are new src scripts or href links, the browser downloads a new resource from the server.

What is the most straightforward approach to comprehending JavaScript?

Checking the cache

Cache-Control: no-store

Your browser should not log anything in the request if you are asked not to log in. You can use it for HTML scripts and Service Worker.

Cache-Control: public, no-cache

or

Cache-Control: public, max-age=0, must-revalidate

These two are equivalent and, despite the cacheless name, allow you to provide cached responses unless the browser does not check to see if the cache is up to date.

If you set the ETag or Last-Modified headers correctly, the browser can check if the cache is already up to date given . The release helps you and your users save bandwidth. You can use it for HTML and Service Worker scripts.

Cache-Control: private, no-cache

or

Cache-Control: private, max-age=0, must-revalidate

Similarly, these two are also equivalent. The difference between public and private is that a shared cache (e.g. CDN) can cache public responses but not private responses.

The local cache (e.g. (e.g. browser) may still cache private responses. You use private when you render your HTML on the server and the rendered HTML contains sensitive or user-specific information.

In terms of structure, this does not it is the case . Case For a typical Gatsby blog, you will need to set Private, but consider doing this with Next.js for pages that require authorized access.

Cache-Control: public, max-age=31536000, immutable

For example , the browser will store the response for one year according to the max-age directive (606024*365).

The immutable directive tells the browser that the contents of this response (file) must not be modified and the browser must not validate its cache by sending If-None-Match (ETag validation) or If-Modified-Since (last modified validation).

Used for your static resources to support long-term caching strategies

Pragma and Expires

Pragma: no-cache
Expires: <http-date>

Pragma is an old header defined as a request header in the HTTP/1.0 specification.

It later became HTTP/ 1.1 The specifies indicates that the response “Pragma: no-cache” should be treated as “Cache-Control: no-cache”, but this is not a reliable substitute because it always acts as the request header.

Also I always use Pragma: no-cache as my OWASP security recommendation.

Add Pragma : Nobody. Caching your cache headers is a precautionary measure that protects older servers that don’t support the latest cache control mechanisms and may be caching what you don’t want to cache.

Some would say you don’t need Prama or Expires unless you need to support Internet Explorer 5 or Netscape. This is similar to older software support.

Proxies often include the Expires header, which gives them a slight advantage.

What are the advanced JavaScript functions to improve code quality?

For HTML files, I leave the Expires header disabled or set it to a past date. For static resources, I manage them with cache-control-max-age via the Nginx expiration directive.

ETag: W/"5e15153d-120f"

or

ETag: "5e15153d-120f"

AND tags are one of many methods of validating the cache. The ETag must uniquely identify the resource and in most cases the web server generates a digital fingerprint from the content of the resource.

If the resource changes , it will look different Sense. . ETag value.

There are two types of ETag. Low ETag equality indicates that the resources are semantically equivalent. Good validation of ETags means that the resources are identical byte for byte.

You can distinguish the two by the “W/” prefix defined for weak ETags.

Weak ETags are not suitable for byte range queries, but can easily be generated during operation.

In practice you will not be the one to configure the ETags and leave them on your web server.

curl -I <http-address>
curl -I -H "Accept-Encoding: gzip" <http-address>

You can see that when requesting a static file, Nginx sets a strong ETag. If gzip compression is enabled but no compressed files are uploaded, instant compression will result in weak ETags.

If the browser uses the If-None-Match request header with the ETag of a cached resource, it waits for a 200 OK response with a new resource or an empty 304 Unmodified response, indicating that you should use a cached resource instead to download a new one.

The same optimization can be applied to GET API responses and is not limited to static files.

If your application receives large JSON payloads, you can configure your backend to calculate and configure ETag from the contents of the payload (e.g. example B . with md5 ).

Before sending it to the client, compare it to the If-None-Match request header.

If there is a match, send 304 Unmodified instead of payload to save bandwidth and improve web application performance.

Last-Modified

Last-Modified: Tue, 07 Jan 2020 23:33:17 GMT

The Last modified response header is another cache control mechanism and uses the last modified date. The Last Modified header is an alternative mechanism for more accurate ETags.

When you send the If-Modified-Since request header with the last modified date of a cached resource, the browser expects a 200 OK response with a more recent resource or a 304 response Empty Unmodified indicating use of the cached resource, rather than downloading a new one.

Debugging

When setting up the headers and then testing the configuration, make sure you are close to your server in terms of networking. What I mean by this is that if you have dockerized your server, run the container and test it locally.

If you are setting up a virtual machine, push it via sss. VM and test the headers there. If you have a Kubernetes cluster, launch a pod and invoke your service within the cluster.

In a production setup, you work with load balancers, proxies and CDNs. Each of these steps can change your headers, so debugging will be much easier if you know that your server sent the correct headers.

An example of behavior unexpected Cloudflare may remove the ETag header if email obfuscation or automatic HTTPS rewriting is enabled.

Good luck finding it, try running the debugging by changing the server configuration. ! In Cloudflare’s defense, this behavior is very well documented and makes a lot of sense. It’s up to you to familiarize yourself with its tools.

Cache-Control: max-age=31536000
Cache-Control: public, immutable

At the beginning of this article, I inserted “the” between the titles of the excerpts to indicate that these are two different examples. Sometimes you may see more than one header in the HTTP response.

This means that both headers are applied. Some proxy servers may merge headers in transit. The example above is:

Cache-Control: max-age=31536000, public, immutable

Using Curl provides more consistent results and easier execution in multiple environments.

If you decide to use a web browser anyway, be sure to watch out for service work debug cache issues. Debugging Service Workers is a complex topic for another article.

To resolve caching issues, be sure to debug Service Workers. Enable the workaround in the Application Development Tools tab.

Nginx Configuration

Now that you understand what the different types of cache headers do, it’s time to focus on putting your knowledge into practice.

The following Nginx configuration works for a single-page application designed to support long-term caching.

gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;

First, I enabled GZIP compression on the content types that would most benefit from a single-page application. For further details on each available gzip configuration, see the Nginx gzip module documentation.

location ~* (\.html|\/sw\.js)$ {
expires -1y;
add_header Pragma "no-cache";
add_header Cache-Control "public";
}

I want to compare all HTML files with /sw.js, a service worker script.

Neither should be cached. The Nginx expiration policy set to a negative value is set in addition to the Expires header and adds an additional cache control: No-Cache Header.

location ~* \.(js|css|png|jpg|jpeg|gif|ico|json)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}

I want to maximize the cache of all my static elements, which are JavaScript files. CSS files, images and static JSON files. If you host your own font files, you can add them too.

location / {
try_files $uri $uri/ =404;
}


if ($host ~* ^www\.(.*)) {
set $host_without_www $1;
rewrite ^(.*) https://$host_without_www$1 permanent;
}

These two have nothing to do with caching, but are an essential part of the Nginx configuration.

Since modern single- source have pretty URLs — It supports routing and my static server doesn’t know it. I need to provide a default index.html file for every path that is not a static file.

I am also interested in URL redirects with www. for the URL without www. You may not need the latter if you host your application somewhere where your service provider already does it for you.

Express Configuration

Sometimes we are unable to serve static files through a reverse proxy server like Nginx.

Your configuration may /serverless service provider limit its use of the most popular programming languages ​​and performance is not your primary concern.

In this case, you may want to use a server like Expresse to serve your static files.

import express, { Response } from "express";
import compression from "compression";
import path from "path";

const PORT = process.env.PORT || 3000;
const BUILD_PATH = "public";

const app = express();

function setNoCache(res: Response) {
const date = new Date();
date.setFullYear(date.getFullYear() - 1);
res.setHeader("Expires", date.toUTCString());
res.setHeader("Pragma", "no-cache");
res.setHeader("Cache-Control", "public, no-cache");
}

function setLongTermCache(res: Response) {
const date = new Date();
date.setFullYear(date.getFullYear() + 1);
res.setHeader("Expires", date.toUTCString());
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
}

app.use(compression());
app.use(
express.static(BUILD_PATH, {
extensions: ["html"],
setHeaders(res, path) {
if (path.match(/(\.html|\/sw\.js)$/)) {
setNoCache(res);
return;
}

if (path.match(/\.(js|css|png|jpg|jpeg|gif|ico|json)$/)) {
setLongTermCache(res);
}
},
}),
);

app.get("*", (req, res) => {
setNoCache(res);
res.sendFile(path.resolve(BUILD_PATH, "index.html"));
});

app.listen(PORT, () => {
console.log(`Server is running http://localhost:${PORT}`);
});

This script mimics what our Nginx setup does. Enable gzip with the compression middleware.

The Express Static middleware sets the ETag and Last-Modified headers for you. We should take care of sending index.html ourselves if the request does not match any known static file.

Examples

Finally I wanted to I’m studying how most popular services use cache headers.

I checked the headers separately for HTML and CSS or JavaScript files. I also looked at the server header (if present) as it can give us some interesting information about the underlying infrastructure.

--

--