Frontend Development 14 min read

Understanding Frontend Caching: HTTP Headers, Browser Cache, and Optimization Techniques

This article explains the mechanisms of frontend caching, covering HTTP cache headers such as Expires and Cache‑Control, browser memory and disk caches, negotiation strategies with ETag and Last‑Modified, and practical NestJS examples to improve web performance.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Understanding Frontend Caching: HTTP Headers, Browser Cache, and Optimization Techniques

Introduction

Frontend caching is a crucial topic for developers seeking to optimize web applications, involving concepts like strong cache, negotiated cache, and cookies. Understanding the underlying mechanisms and timing formulas enables more effective use of caching to boost performance.

HTTP Cache

HTTP operates over TCP and its network panel shows two parts: Response Headers and Request Headers . Important cache‑related header fields include:

Expires

The Expires header specifies an absolute date/time after which the response is considered stale. If Cache‑Control contains max‑age or s‑maxage , the Expires header is ignored.

Expires: Wed, 24 Apr 2024 14:27:26 GMT

Cache‑Control

Cache‑Control (defined in HTTP/1.1) can combine directives such as max‑age , s‑maxage , public / private , no‑cache , and no‑store .

Cache‑Control: max-age=3600, s-maxage=3600, public

max‑age is relative to the current time (seconds) and overrides Expires . s‑maxage applies only to shared caches (e.g., proxy servers). public allows any cache to store the response, while private restricts it to the client; setting private disables s‑maxage . no‑store disables caching entirely, and no‑cache forces revalidation with the server before using a cached copy.

Cache directives can also be set via a meta tag in HTML:

<meta http-equiv="Cache‑Control" content="no‑cache" />

Example with NestJS

The following NestJS controller sets an Expires header for 10 seconds and returns a large JSON payload:

@Get('/getdata')
getData(@Response() res: Res) {
  return res.set({
    'Expires': new Date(Date.now() + 10).toUTCString()
  }).json({
    list: new Array(1000000).fill(1).map((item, index) => ({ index, item: 'index' + index }))
  });
}

The first request takes about 334 ms; the second, served from disk cache, takes roughly 163 ms, nearly 50 % faster.

Changing the controller to also send Cache‑Control: 1 disables caching, causing each request to hit the server.

@Get('/getdata')
getData(@Response() res: Res) {
  return res.set({
    'Expires': new Date(Date.now() + 10).toUTCString(),
    'Cache‑Control': 1
  }).json({
    list: new Array(1000000).fill(1).map((item, index) => ({ index, item: 'index' + index }))
  });
}

HPACK Compression (HTTP/2)

HTTP/2 uses HPACK, which combines static Huffman tables and a dynamic table to compress header fields by indexing them, greatly reducing overhead.

Last‑Modified & If‑Modified‑Since

Last‑Modified indicates the resource’s last change time. Browsers store this value and send If‑Modified‑Since on subsequent requests; if the resource hasn’t changed, the server replies with 304 Not Modified .

Last‑Modified: Fri, 14 May 2021 17:23:13 GMT
If‑Modified‑Since: Fri, 14 May 2021 17:23:13 GMT

ETag & If‑None‑Match

ETag is a unique identifier for a resource version. Browsers send If‑None‑Match with the stored ETag; a mismatch triggers a fresh response, while a match results in 304 Not Modified .

ETag: "29322-09SpAhH3nXWd8KIVqB10hSSz66"
If‑None‑Match: "29322-09SpAhH3nXWd8KIVqB10hSSz66"

Strong Cache

Strong cache relies on Expires (absolute time) or max‑age (relative seconds). Both can become inaccurate if the client’s clock is altered.

Freshness Formula

Cache freshness can be expressed as:

fresh = (creationTime + (expires || max‑age)) > currentTime

Where creationTime is the response time, and the usage period includes response time, transmission latency, and time spent in cache.

Negotiated Cache

Negotiated cache uses ETag and Last‑Modified to validate resources. The ETag generation in Node.js (via the etag package) can be based on file content and modification time or on a hash of the content and length.

Browser Cache

Browsers store resources in memory cache (fast, cleared on tab close) or disk cache (slower, persists). Retrieval order: memory → disk → network.

Large assets like images are usually cached on disk, while small inline resources may stay in memory. Scripts injected during HTML parsing tend to be cached in memory; dynamically loaded scripts are often stored on disk.

Preload vs. Prefetch

preload (via <link rel="preload"> ) tells the browser to fetch resources immediately for the current page, typically reading from disk cache. prefetch (via <link rel="prefetch"> ) hints that a resource will be needed on a future navigation, allowing the browser to download it in idle time and store it in cache.

<link rel="preload" href="//example.com/script.js" as="script">
<link rel="prefetch" href="//example.com/next-page.js">
frontendperformancecachingHTTPbrowsercache controlETag
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.