From 36ae335fa6fb8f925c9774d429fa5a25d10752c9 Mon Sep 17 00:00:00 2001 From: Connor Tsui Date: Tue, 7 Jul 2026 18:05:57 +0100 Subject: [PATCH] fix(web): bound read-API CDN staleness at an hour The edge cache is URL-keyed and tag-blind: /api/revalidate flushes the server-side Data Cache but cannot purge already-cached CDN responses, which the day-scale stale-while-revalidate window let serve pre-rewrite data for up to 24 hours after the 2026-07-07 historical backfill. An hour bounds that skew at the cost of at most one extra function invocation per URL per hour. Co-Authored-By: Claude Fable 5 --- web/lib/cache.test.ts | 4 ++-- web/lib/cache.ts | 16 +++++++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/web/lib/cache.test.ts b/web/lib/cache.test.ts index fde8fc8..5724e58 100644 --- a/web/lib/cache.test.ts +++ b/web/lib/cache.test.ts @@ -6,8 +6,8 @@ import { describe, expect, it } from 'vitest'; import { READ_API_CACHE_CONTROL } from './cache'; describe('READ_API_CACHE_CONTROL', () => { - it('keeps a 5-minute fresh window but allows day-scale stale-while-revalidate', () => { + it('keeps a 5-minute fresh window but allows hour-scale stale-while-revalidate', () => { expect(READ_API_CACHE_CONTROL).toContain('s-maxage=300'); - expect(READ_API_CACHE_CONTROL).toContain('stale-while-revalidate=86400'); + expect(READ_API_CACHE_CONTROL).toContain('stale-while-revalidate=3600'); }); }); diff --git a/web/lib/cache.ts b/web/lib/cache.ts index f2c32b5..06a60bb 100644 --- a/web/lib/cache.ts +++ b/web/lib/cache.ts @@ -4,14 +4,20 @@ /** * `Cache-Control` for the read-API 200 responses: Vercel's edge CDN caches the response keyed by * the FULL request URL (so each `?n=` window is its own cache entry) for five minutes, matching - * v2's S3 refresh cadence, and may serve it stale for up to a day while it revalidates in the - * background. The site is low-traffic, so the longer stale window keeps repeat visits on the CDN - * instead of paying a cold function start. Error responses (400/404/500) deliberately omit this - * header so they are never CDN-cached. + * v2's S3 refresh cadence, and may serve it stale for up to an hour while it revalidates in the + * background. The site is low-traffic, so the stale window keeps repeat visits on the CDN instead + * of paying a cold function start. Error responses (400/404/500) deliberately omit this header so + * they are never CDN-cached. + * + * The stale window was a day until the 2026-07-07 historical backfill made its cost visible: the + * edge cache is URL-keyed and tag-blind, so after a write that CHANGES history (rather than + * appending the newest commit, where a stale serve is indistinguishable from a fresh one), each + * URL could keep serving pre-rewrite data for up to a day. One hour bounds that skew at a cost of + * at most one extra function invocation per URL per hour. * * This replaces the route-segment `export const revalidate = 300` approach, which cannot express * the intended behavior: on handlers that read `request.url` the export is inert (the request-time * URL access forces dynamic rendering), and on parameterless handlers it forces a BUILD-time * prerender, making `next build` fail without a reachable database. */ -export const READ_API_CACHE_CONTROL = 'public, s-maxage=300, stale-while-revalidate=86400'; +export const READ_API_CACHE_CONTROL = 'public, s-maxage=300, stale-while-revalidate=3600';