diff --git a/src/pages/downloads/folia.astro b/src/pages/downloads/folia.astro index 3e9749ca..227bc6de 100644 --- a/src/pages/downloads/folia.astro +++ b/src/pages/downloads/folia.astro @@ -3,10 +3,11 @@ export const prerender = false; import { env } from "cloudflare:workers"; import Layout from "@/layouts/Layout.astro"; import SoftwareDownloadPage from "@/components/data/SoftwareDownloadPage.svelte"; -import { fetchDownloadsPageSnapshot } from "@/utils/download"; +import { downloadsPageCacheTag, fetchDownloadsPageSnapshot } from "@/utils/download"; import { coordinatorStub } from "@/downloads-live"; -Astro.response.headers.set("Cache-Control", "private, max-age=60"); +Astro.response.headers.set("Cache-Control", "public, max-age=300, s-maxage=600, stale-while-revalidate=120"); +Astro.response.headers.set("Cache-Tag", downloadsPageCacheTag("folia")); const snapshot = await fetchDownloadsPageSnapshot("folia", env.WEBSITE_CACHE, () => coordinatorStub(env, "folia")); --- diff --git a/src/pages/downloads/paper.astro b/src/pages/downloads/paper.astro index 985e4981..becfcf00 100644 --- a/src/pages/downloads/paper.astro +++ b/src/pages/downloads/paper.astro @@ -3,10 +3,11 @@ export const prerender = false; import { env } from "cloudflare:workers"; import Layout from "@/layouts/Layout.astro"; import SoftwareDownloadPage from "@/components/data/SoftwareDownloadPage.svelte"; -import { fetchDownloadsPageSnapshot } from "@/utils/download"; +import { downloadsPageCacheTag, fetchDownloadsPageSnapshot } from "@/utils/download"; import { coordinatorStub } from "@/downloads-live"; -Astro.response.headers.set("Cache-Control", "private, max-age=60"); +Astro.response.headers.set("Cache-Control", "public, max-age=300, s-maxage=600, stale-while-revalidate=120"); +Astro.response.headers.set("Cache-Tag", downloadsPageCacheTag("paper")); const snapshot = await fetchDownloadsPageSnapshot("paper", env.WEBSITE_CACHE, () => coordinatorStub(env, "paper")); --- diff --git a/src/pages/downloads/velocity.astro b/src/pages/downloads/velocity.astro index c1bedea8..615014b1 100644 --- a/src/pages/downloads/velocity.astro +++ b/src/pages/downloads/velocity.astro @@ -3,10 +3,11 @@ export const prerender = false; import { env } from "cloudflare:workers"; import Layout from "@/layouts/Layout.astro"; import SoftwareDownloadPage from "@/components/data/SoftwareDownloadPage.svelte"; -import { fetchDownloadsPageSnapshot } from "@/utils/download"; +import { downloadsPageCacheTag, fetchDownloadsPageSnapshot } from "@/utils/download"; import { coordinatorStub } from "@/downloads-live"; -Astro.response.headers.set("Cache-Control", "private, max-age=60"); +Astro.response.headers.set("Cache-Control", "public, max-age=300, s-maxage=600, stale-while-revalidate=120"); +Astro.response.headers.set("Cache-Tag", downloadsPageCacheTag("velocity")); const snapshot = await fetchDownloadsPageSnapshot("velocity", env.WEBSITE_CACHE, () => coordinatorStub(env, "velocity")); --- diff --git a/src/pages/downloads/waterfall.astro b/src/pages/downloads/waterfall.astro index facba80d..9e930c40 100644 --- a/src/pages/downloads/waterfall.astro +++ b/src/pages/downloads/waterfall.astro @@ -3,10 +3,11 @@ export const prerender = false; import { env } from "cloudflare:workers"; import Layout from "@/layouts/Layout.astro"; import SoftwareDownloadPage from "@/components/data/SoftwareDownloadPage.svelte"; -import { fetchDownloadsPageSnapshot } from "@/utils/download"; +import { downloadsPageCacheTag, fetchDownloadsPageSnapshot } from "@/utils/download"; import { coordinatorStub } from "@/downloads-live"; -Astro.response.headers.set("Cache-Control", "private, max-age=60"); +Astro.response.headers.set("Cache-Control", "public, max-age=300, s-maxage=600, stale-while-revalidate=120"); +Astro.response.headers.set("Cache-Tag", downloadsPageCacheTag("waterfall")); const snapshot = await fetchDownloadsPageSnapshot("waterfall", env.WEBSITE_CACHE, () => coordinatorStub(env, "waterfall")); const descriptionHtml = ` diff --git a/src/pages/webhooks/fill.ts b/src/pages/webhooks/fill.ts index d213936b..daf3b4d7 100644 --- a/src/pages/webhooks/fill.ts +++ b/src/pages/webhooks/fill.ts @@ -1,5 +1,5 @@ import type { APIRoute } from "astro"; -import { env } from "cloudflare:workers"; +import { env, exports } from "cloudflare:workers"; import { isDownloadProjectId } from "@/utils/download"; import { requestDownloadsRefresh } from "@/downloads-live"; @@ -67,7 +67,7 @@ export const POST: APIRoute = async ({ request, locals }) => { return acknowledge(); } - const refresh = requestDownloadsRefresh(env, project, deliveryId); + const refresh = requestDownloadsRefresh(env, project, deliveryId).then(() => exports.DownloadsPages.invalidate(project)); if (locals.cfContext) { locals.cfContext.waitUntil(refresh); } else { diff --git a/src/utils/download.ts b/src/utils/download.ts index 4779545e..348b5fed 100644 --- a/src/utils/download.ts +++ b/src/utils/download.ts @@ -13,6 +13,14 @@ export function isDownloadProjectId(value: unknown): value is DownloadProjectId return typeof value === "string" && DOWNLOAD_PROJECT_ID_SET.has(value); } +export function downloadsPagePath(project: DownloadProjectId): `/downloads/${DownloadProjectId}` { + return `/downloads/${project}`; +} + +export function downloadsPageCacheTag(project: DownloadProjectId): `downloads:${DownloadProjectId}` { + return `downloads:${project}`; +} + export function downloadRegionForContinent(continent?: string): DownloadRegion { if (continent === "EU" || continent === "AF") return "weur"; if (continent === "AS" || continent === "OC") return "apac"; diff --git a/src/worker.ts b/src/worker.ts index 8b541e72..3a0bec68 100644 --- a/src/worker.ts +++ b/src/worker.ts @@ -1,5 +1,12 @@ import { handle } from "@astrojs/cloudflare/handler"; -import { DOWNLOAD_PROJECT_IDS, isDownloadProjectId } from "./utils/download"; +import { WorkerEntrypoint } from "cloudflare:workers"; +import { + DOWNLOAD_PROJECT_IDS, + downloadsPageCacheTag, + downloadsPagePath, + isDownloadProjectId, + type DownloadProjectId, +} from "./utils/download"; import { PAPER_PLAYERCOUNT_KEY, fetchPaperBstatsPlayerCount } from "./utils/bstats"; import { DOWNLOADS_LIVE_PATH } from "./utils/downloads-live-path"; import { @@ -14,6 +21,26 @@ export { DownloadsUpdateCoordinator, DownloadsWebSocketShard }; const PLAYER_COUNT_CRON = "*/10 * * * *"; const DOWNLOADS_RECONCILIATION_CRON = "0 * * * *"; +const DOWNLOADS_PAGE_PATHS: ReadonlySet = new Set(DOWNLOAD_PROJECT_IDS.map(downloadsPagePath)); + +/** + * The cache-enabled Worker entrypoint for dynamic download pages. + * + * Keep the default entrypoint uncached: it also serves assets, whose otherwise + * free requests become billed when Workers Cache is enabled for that entrypoint. + */ +export class DownloadsPages extends WorkerEntrypoint { + override fetch(request: Request): Promise { + return handle(request, this.env, this.ctx); + } + + async invalidate(project: DownloadProjectId): Promise { + const cache = this.ctx.cache; + if (!cache) throw new Error("Workers Cache is not enabled for the downloads pages entrypoint"); + const result = await cache.purge({ tags: [downloadsPageCacheTag(project)] }); + if (!result.success) throw new Error(`Failed to purge ${project} downloads page cache`); + } +} export default { async fetch(request, env, ctx) { @@ -25,21 +52,25 @@ export default { url.searchParams.set("region", region); return downloadShardStub(env, project, region).fetch(new Request(url, request)); } + if (DOWNLOADS_PAGE_PATHS.has(url.pathname)) { + return ctx.exports.DownloadsPages.fetch(request); + } return handle(request, env, ctx); }, - async scheduled(controller, env, _ctx) { + async scheduled(controller, env, ctx) { if (controller.cron === PLAYER_COUNT_CRON) { await updateStatsCache(env); } else if (controller.cron === DOWNLOADS_RECONCILIATION_CRON) { - await updateDownloadsPageCache(env); + await updateDownloadsPageCache(env, ctx); } }, } satisfies ExportedHandler; -async function updateDownloadsPageCache(env: Env) { +async function updateDownloadsPageCache(env: Env, ctx: ExecutionContext) { for (const project of DOWNLOAD_PROJECT_IDS) { try { await requestDownloadsRefresh(env, project); + await ctx.exports.DownloadsPages.invalidate(project); } catch (error) { console.error( JSON.stringify({ diff --git a/wrangler.jsonc b/wrangler.jsonc index 563b2796..bfc175ae 100644 --- a/wrangler.jsonc +++ b/wrangler.jsonc @@ -4,6 +4,23 @@ "main": "./src/worker.ts", "compatibility_date": "2026-02-19", "compatibility_flags": ["nodejs_compat", "disable_nodejs_process_v2"], + "cache": { + "enabled": true, + }, + "exports": { + "default": { + "type": "worker", + "cache": { + "enabled": false, + }, + }, + "DownloadsPages": { + "type": "worker", + "cache": { + "enabled": true, + }, + }, + }, "assets": { "binding": "ASSETS", "directory": "./dist",