From 57743e6640d23d37ae6c94e725777a9889592e15 Mon Sep 17 00:00:00 2001 From: Jonas Braathen Date: Sat, 5 Sep 2026 18:09:04 +0200 Subject: [PATCH] web: new attempt at fixing revalidation/caching per day --- web/src/lib/revalidation.test.ts | 31 ++++++++++++++++++++++++++++++- web/src/lib/revalidation.ts | 10 +++++++--- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/web/src/lib/revalidation.test.ts b/web/src/lib/revalidation.test.ts index d45ad53..e4523be 100644 --- a/web/src/lib/revalidation.test.ts +++ b/web/src/lib/revalidation.test.ts @@ -1,6 +1,9 @@ import { describe, expect, it } from "vitest"; -import { secondsUntilOsloMidnight } from "./revalidation.ts"; +import { + cachedUntilOsloMidnight, + secondsUntilOsloMidnight, +} from "./revalidation.ts"; describe("secondsUntilOsloMidnight", () => { it("is 12h from midnight at 12:00 CEST on a normal day", () => { @@ -23,3 +26,29 @@ describe("secondsUntilOsloMidnight", () => { expect(secondsUntilOsloMidnight(now)).toBe(23 * 3600); }); }); + +describe("cachedUntilOsloMidnight", () => { + // urql types fetchOptions as RequestInit | () => RequestInit; we always pass an object + function fetchOptionsAt(now: Date): RequestInit { + const { fetchOptions } = cachedUntilOsloMidnight(now); + return typeof fetchOptions === "function" ? fetchOptions() : fetchOptions ?? {}; + } + + it("keys the fetch cache on the Oslo date, not the UTC date", () => { + // 00:30 CEST on Sep 5 is still Sep 4 in UTC + const { headers } = fetchOptionsAt(new Date("2026-09-04T22:30:00Z")); + expect(headers).toEqual({ "x-oslo-day": "2026-09-05" }); + }); + + it("changes the cache key at Oslo midnight", () => { + const before = fetchOptionsAt(new Date("2026-09-04T21:59:59Z")); + const after = fetchOptionsAt(new Date("2026-09-04T22:00:00Z")); + expect(before.headers).toEqual({ "x-oslo-day": "2026-09-04" }); + expect(after.headers).toEqual({ "x-oslo-day": "2026-09-05" }); + }); + + it("revalidates at Oslo midnight and tags the entry", () => { + const { next } = fetchOptionsAt(new Date("2026-09-05T10:00:00Z")); + expect(next).toEqual({ revalidate: 12 * 3600, tags: ["cms"] }); + }); +}); diff --git a/web/src/lib/revalidation.ts b/web/src/lib/revalidation.ts index 34e7b24..34bb19f 100644 --- a/web/src/lib/revalidation.ts +++ b/web/src/lib/revalidation.ts @@ -1,5 +1,5 @@ import { addDays, startOfDay } from "date-fns"; -import { fromZonedTime, toZonedTime } from "date-fns-tz"; +import { formatInTimeZone, fromZonedTime, toZonedTime } from "date-fns-tz"; import type { OperationContext } from "@urql/core"; const timeZone = "Europe/Oslo"; @@ -29,10 +29,14 @@ export const uncached: Partial = { }; // For queries whose results depend on "today" (futureEvents, opening hours) -export function cachedUntilOsloMidnight(): Partial { +export function cachedUntilOsloMidnight( + now: Date = new Date() +): Partial { return { fetchOptions: { - next: { revalidate: secondsUntilOsloMidnight(), tags: [CMS_CACHE_TAG] }, + // Part of the fetch cache key, so yesterday's entry is never reused + headers: { "x-oslo-day": formatInTimeZone(now, timeZone, "yyyy-MM-dd") }, + next: { revalidate: secondsUntilOsloMidnight(now), tags: [CMS_CACHE_TAG] }, }, }; }