Our Personal Data Server from scratch! tranquil.farm

fix: cache headers and add etags #646

Open quilling.dev
targeting main from quilling.dev/tranquil-pds:fix/cache-header-etags
Open opened by and assigned to quilling.dev
targeting main from quilling.dev/tranquil-pds:fix/cache-header-etags

has this ever happened to YOU?

you have merged some new PRs for tranquil and rebuilt it on your nixos system. these include some changes for the frontend account manager

you tried to re-login to the account manager to check things out but you just saw a blank page??

(clearing your cache will fix it, for reasons explained below)

curl -sI https://ao.oyster.cafe/app/dashboard

HTTP/2 200 
accept-ranges: bytes 
alt-svc: h3=":443"; ma=2592000 
content-type: text/html 
date: Wed, 07 Oct 2026 19:28:48 GMT 
last-modified: Thu, 01 Jan 1970 00:00:01 GMT 
via: 1.1 Caddy 
content-length: 579 

this is caused by something nix and others do to attain reproducible builds. instead of using mtime (modification time) with actual datetimestamps, it uses unix epoch, to make sure builds built at different real-world timestamps still have the same bytes for the package.

this results in the Last-Modified header showing an inaccurate timestamp. we also dont ship any Cache-Control header, so the timestamp alone gets used by browsers to determine how long to cache the page. they make the assumption that a webpage from ~56 years ago likely doesn't change very often and might decide to cache it for years.

when the underlying pages have changed, but the browser still thinks its cache is valid, the frontend can break!

two things can help fix this behavior:

  • supplying Cache-Control: no-cache, which asks clients to always check upstream for changes
  • supplying an ETag, allowing clients to compare its value with upstream instead of the Last-Modified date

when a client checks upstream for changes, even under no-cache, it has an opportunity to be told 304: Not Modified and keep the local copy:

curl -sI -H 'If-Modified-Since: Thu, 01 Jan 1970 00:00:01 GMT' https://ao.oyster.cafe/app/dashboard 

HTTP/2 304 
alt-svc: h3=":443"; ma=2592000 
date: Wed, 07 Oct 2026 20:14:19 GMT 
via: 1.1 Caddy 

by default it compares the unix epoch Last-Modified fields which never change and always 304s. we can instead have tranquil generate unique identifiers based on the content of the resource (SHA-256). we will have clients use these "entity tags" instead of the Last-Modified header

effectively, this makes sure that clients are always asking upstream if it's updated, and re-fetching when (and only when) it has changed

etagged routes:

  • /
  • /app/*
  • /index.html
  • /homepage.html

hashed assets provide public, max-age=31536000, immutable for 2xx/304 responses, as they are uniquely hashed by name already. 404s do not get cached

bonus: token responses and PAR now provide no-store and no-cache. clients were likely not caching these anyway but this puts us in compliance with RFC 6749 5.1 and mirrors the examples on RFC 9126 2.2

placing nginx/caddy/cloudflare in front of tranquil should be compatible with these tags. if for example using compression in caddy, the etag headers get the compression appended to them, like -gzip. I believe nginx and cloudflrae downgrades the etags to "weak validation" in this scenario.

two-thirds of this pr is just tests that enshrine this behavior in the new frontend.rs module

Commits 0