HTTP Caching 101
================
it's actually pretty straightforward
Simple `GET`
------------
```http
GET /bundle.js HTTP/1.1
Accept: */*
```
Simple `GET`
------------
```http
GET /bundle.js HTTP/1.1
Accept: */*
```
```http
HTTP/1.1 200 OK
Content-Type: application/javascript
Content-Length: 87933
```
Simple `GET`
------------
```http
GET /bundle.js HTTP/1.1
Accept: */*
```
```http
HTTP/1.1 200 OK
Content-Type: application/javascript
Content-Length: 87933
(function() {
```
Simple `GET`
------------
```http
GET /bundle.js HTTP/1.1
Accept: */*
```
```http
HTTP/1.1 200 OK
Content-Type: application/javascript
Content-Length: 87933
(function() {
…
```
Simple `GET`
------------
```http
GET /bundle.js HTTP/1.1
Accept: */*
```
```http
HTTP/1.1 200 OK
Content-Type: application/javascript
Content-Length: 87933
(function() {
…
}())
```
Simple `GET` ♺
--------------
```http
GET /bundle.js HTTP/1.1
Accept: */*
```
Simple `GET` ♺
--------------
```http
GET /bundle.js HTTP/1.1
Accept: */*
```
```http
HTTP/1.1 200 OK
Content-Type: application/javascript
Content-Length: 87933
```
Simple `GET` ♺
--------------
```http
GET /bundle.js HTTP/1.1
Accept: */*
```
```http
HTTP/1.1 200 OK
Content-Type: application/javascript
Content-Length: 87933
(function() {
```
Simple `GET` ♺
--------------
```http
GET /bundle.js HTTP/1.1
Accept: */*
```
```http
HTTP/1.1 200 OK
Content-Type: application/javascript
Content-Length: 87933
(function() {
…
```
Simple `GET` ♺
--------------
```http
GET /bundle.js HTTP/1.1
Accept: */*
```
```http
HTTP/1.1 200 OK
Content-Type: application/javascript
Content-Length: 87933
(function() {
…
}())
```
Simple `GET` ♺
--------------
```http
GET /bundle.js HTTP/1.1
Accept: */*
```
```http
HTTP/1.1 200 OK
Content-Type: application/javascript
Content-Length: »87933«
(function() {
…
}())
```
⇒ duplicate download (if the resource hasn't changed)
Conditional `GET`
-----------------
```http
GET /bundle.js HTTP/1.1
Accept: */*
```
```http
HTTP/1.1 200 OK
Content-Type: application/javascript
Content-Length: 87933
(function() {
…
}())
```
Conditional `GET` 🏷️
-------------------
```http
GET /bundle.js HTTP/1.1
Accept: */*
```
```http
HTTP/1.1 200 OK
Content-Type: application/javascript
Content-Length: 87933
ETag: "66dbb50c6c7535374984e5fccef39d71"
(function() {
…
}())
```
Conditional `GET` 🏷️ ♺
----------------------
```http
GET /bundle.js HTTP/1.1
Accept: */*
```
Conditional `GET` 🏷️ ♺
----------------------
```http
GET /bundle.js HTTP/1.1
Accept: */*
If-None-Match: "66dbb50c6c7535374984e5fccef39d71"
```
Conditional `GET` 🏷️ ♺
----------------------
```http
GET /bundle.js HTTP/1.1
Accept: */*
If-None-Match: "66dbb50c6c7535374984e5fccef39d71"
```
```http
HTTP/1.1 304 Not Modified
```
⇒ saves downloading ~90 kB multiple times
however, still requires a request - thus latency (network + server response)
Conditional `GET` 🕰️
-------------------
```http
GET /bundle.js HTTP/1.1
Accept: */*
```
```http
HTTP/1.1 200 OK
Content-Type: application/javascript
Content-Length: 87933
ETag: "66dbb50c6c7535374984e5fccef39d71"
(function() { … }())
```
Conditional `GET` 🕰️
-------------------
```http
GET /bundle.js HTTP/1.1
Accept: */*
```
```http
HTTP/1.1 200 OK
Content-Type: application/javascript
Content-Length: 87933
ETag: "66dbb50c6c7535374984e5fccef39d71"
Last-Modified: Mon, 5 June 2018 16:28:35 GMT
(function() { … }())
```
both headers/mechanisms are mutually independent
(`Last-Modified` also provides caching-indepenent semantic value)
Conditional `GET` 🕰️ ♺
----------------------
```http
GET /bundle.js HTTP/1.1
Accept: */*
If-None-Match: "66dbb50c6c7535374984e5fccef39d71"
```
Conditional `GET` 🕰️ ♺
----------------------
```http
GET /bundle.js HTTP/1.1
Accept: */*
If-None-Match: "66dbb50c6c7535374984e5fccef39d71"
If-Modified-Since: Mon, 5 June 2018 16:28:35 GMT
```
both headers/mechanisms are mutually independent
client may construct arbitrary timestamp (e.g. periodic sync)
Conditional `GET` 🕰️ ♺
----------------------
```http
GET /bundle.js HTTP/1.1
Accept: */*
If-None-Match: "66dbb50c6c7535374984e5fccef39d71"
If-Modified-Since: Mon, 5 June 2018 16:28:35 GMT
```
```http
HTTP/1.1 304 Not Modified
```
Resource Expiry
---------------
```http
GET /bundle.js HTTP/1.1
Accept: */*
```
```http
HTTP/1.1 200 OK
Content-Type: application/javascript
Content-Length: 87933
ETag: "66dbb50c6c7535374984e5fccef39d71"
Last-Modified: Mon, 5 June 2018 16:28:35 GMT
(function() { … }())
```
Resource Expiry
---------------
```http
GET /bundle.js HTTP/1.1
Accept: */*
```
```http
HTTP/1.1 200 OK
Content-Type: application/javascript
Content-Length: 87933
ETag: "66dbb50c6c7535374984e5fccef39d71"
Last-Modified: Mon, 5 June 2018 16:28:35 GMT
Expires: Sat, 30 June 2018 23:59:59 GMT
(function() { … }())
```
Resource Expiry
---------------
```http
GET /bundle.js HTTP/1.1
Accept: */*
```
```http
HTTP/1.1 200 OK
Content-Type: application/javascript
Content-Length: 87933
ETag: "66dbb50c6c7535374984e5fccef39d71"
Last-Modified: Mon, 5 June 2018 16:28:35 GMT
Expires: Sat, 30 June 2018 23:59:59 GMT
(function() { … }())
```
⇒ no more network requests until the end of the month
⇒ avoids latency altogether
Resource Expiry
---------------
```http
GET /bundle.js HTTP/1.1
Accept: */*
```
```http
HTTP/1.1 200 OK
Content-Type: application/javascript
Content-Length: 87933
ETag: "66dbb50c6c7535374984e5fccef39d71"
Last-Modified: Mon, 5 June 2018 16:28:35 GMT
Cache-Control: max-age=300
(function() { … }())
```
⇒ no more network requests within 5 min.
----
❓ `ETag`/`Last-Modified` merely a fallback, just in case `Cache-Control` is unsupported?
Immutable Resources
-------------------
```http
GET /bundle.js HTTP/1.1
Accept: */*
```
```http
HTTP/1.1 200 OK
Content-Type: application/javascript
Content-Length: 87933
ETag: "66dbb50c6c7535374984e5fccef39d71"
Last-Modified: Mon, 5 June 2018 16:28:35 GMT
Cache-Control: max-age=31536000
(function() { … }())
```
⇒ no more network requests within a year (modulo client expiry)
----
`max-age`
[should not exceed one year](https://tools.ietf.org/html/rfc7234#section-5.3)
Immutable Resources
-------------------
```http
GET /bundle.js HTTP/1.1
Accept: */*
```
```http
HTTP/1.1 200 OK
Content-Type: application/javascript
Content-Length: 87933
ETag: "66dbb50c6c7535374984e5fccef39d71"
Last-Modified: Mon, 5 June 2018 16:28:35 GMT
Cache-Control: max-age=31536000, immutable
(function() { … }())
```
⇒ no more network requests, ever (modulo client expiry)
☝️ `immutable` not universally supported yet; `max-age` ensures backwards compatibility
Immutable Resources
-------------------
```http
GET /bundle-a3d88d078ad305273d6c3c0467358016.js HTTP/1.1
Accept: */*
```
```http
HTTP/1.1 200 OK
Content-Type: application/javascript
Content-Length: 87933
ETag: "66dbb50c6c7535374984e5fccef39d71"
Last-Modified: Mon, 5 June 2018 16:28:35 GMT
Cache-Control: max-age=31536000, immutable
(function() { … }())
```
fingerprinting allows for cache busting
Volatile Resources
------------------
```http
GET /bundle.js HTTP/1.1
Accept: */*
```
```http
HTTP/1.1 200 OK
Content-Type: application/javascript
Content-Length: 87933
Cache-Control: no-cache, must-revalidate, max-age=0
(function() { … }())
```
prohibits caching
Stale Resources
---------------
```http
GET /bundle.js HTTP/1.1
Accept: */*
```
```http
HTTP/1.1 200 OK
Content-Type: application/javascript
Content-Length: 87933
Cache-Control: max-age=60, stale-while-revalidate=31556926
(function() { … }())
```
serves potentially stale resource from cache while updating cache in the
background
Private Resources
-----------------
```http
GET /dashboard HTTP/1.1
Accept: */*
```
```http
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 2174
Cache-Control: max-age=31536000
…
```
personalized or sensitive content might not be suitable for shared caches
----
* shared cache: e.g. intermediate proxies
* private cache: user agents
Private Resources
-----------------
```http
GET /dashboard HTTP/1.1
Accept: */*
```
```http
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 2174
Cache-Control: private, max-age=31536000
…
```
Tips
----
* avoid `Expires`; outdated dead weight
* `Cache-Control`: `public` vs. `private` hints (e.g. for sensitive information)
* `Vary: Accept` avoids confusion with content negotiation
* beware of real-world behaviors (spec vs. implementations)
* beware of snake oil
Resources
---------
* Souders: Cache Is King
* [video](https://www.youtube.com/watch?v=HKNZ-tQQnSY)
* [slides](https://www.slideshare.net/souders/cache-is-king)
([PPT](https://stevesouders.com/docs/html5dev-cacheisking-20121015.pptx))
* [blog post](https://www.stevesouders.com/blog/2012/10/11/cache-is-king/)
→ Caching 101 (pp. 30 ff. / 00:10:41) served as inspiration here
* [Caching best practices & max-age gotchas](https://jakearchibald.com/2016/caching-best-practices/)
* real-world analysis
* [The headers we want](https://www.fastly.com/blog/headers-we-want)
* [The headers we don't want](https://www.fastly.com/blog/headers-we-dont-want)
EOF