Let caches hold the public reads for a minute - #213
Merged
Conversation
Only /v1/homepage stated a freshness bound, so every other public read was left to whatever heuristic an intermediary picked -- and Scalene's fetch cache has no expiry to respect at all, which is the same reason the homepage got a header in the first place. Article detail, the section and subsection listings, author listings, comment threads, the sitemap and the site settings now carry the same bound. Routes behind OptionalAuth answer two audiences at one URL: anonymously the published, non-archived view, and to a signed-in editor the same URL also carries drafts and soft-deleted rows. Those get the header only when the request is anonymous; a request carrying credentials is marked private, no-store, because a shared cache holding an editor's copy would serve unpublished headlines to readers. Vary names what distinguishes them, added rather than set so the compression middleware's own Vary: Accept-Encoding survives. Note that Cloudflare honours Vary only for Accept-Encoding, so the Vary here protects standards-compliant caches and Scalene; a CDN rule that caches this API has to bypass on cookie rather than rely on it. That is recorded on the helper. The header is set immediately before the successful write, so 404s and 500s stay uncacheable -- a slug that 404s for sixty seconds because a reader beat the publish is a support ticket. The homepage's own constant is gone; it and everything else now share publicReadCacheControl, which carries the bound the homepage needed. Verified against the corpus: response bodies are byte-identical to main across all eleven endpoints, so this is headers and nothing else. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #212. Only
/v1/homepagestated a freshness bound, soeverything else was left to whatever heuristic an intermediary picked —
and Scalene's fetch cache has no expiry to respect at all, which is why
the homepage got a header in the first place.
public, max-age=60, stale-while-revalidate=300(the bound the homepagealready used) now covers:
/v1/articlesand/v1/articles/{slug}/v1/sections/{slug}/articles,/v1/subsections/{slug}/articles/v1/authors/{slug}/articles/v1/articles/{slug}/comments/v1/sitemap/slugs/v1/settings/{site,footer,seo,breaking-news,homepage-carousel}The part worth reviewing
Five of those sit behind
OptionalAuthand answer two audiences at oneURL — anonymously the published view, and to a signed-in editor the same
URL also carries drafts and soft-deleted rows. Marking an editor's copy
publicwould let a shared cache serve unpublished headlines to readers.So the header depends on the caller: anonymous gets the public bound, a
request carrying credentials gets
private, no-store.Vary: Cookie, Authorizationnames the difference, added rather than set so thecompression middleware's
Vary: Accept-Encodingsurvives.Cloudflare caveat, recorded on the helper: CF honours
Varyonly forAccept-Encoding. TheVaryhere protects standards-compliant caches andScalene's fetch cache — a CDN rule that caches this API needs to bypass on
cookie rather than rely on it.
Deliberately excluded
/v1/articles/random— caching it defeats the endpoint./v1/search— query-string cardinality makes the hit rate poor.RequireAuth— no public form to cache.Verification
against the real corpus. This is headers and nothing else.
Cache-Control: the header is set immediatelybefore the successful write, not at handler entry. A slug that 404s for
sixty seconds because a reader beat the publish is a support ticket.
fail loudly if the draft isn't in the body so they can't pass vacuously.
go vetclean, full suite including MariaDB integration tests passes.🤖 Generated with Claude Code