From 5229ac674b33a6d6c97c68fa7ae1647ac183ad3d Mon Sep 17 00:00:00 2001 From: Florent Tapponnier Date: Sat, 18 Jul 2026 19:37:12 +0200 Subject: [PATCH] =?UTF-8?q?cherry-pick:=20revalidate-aggregate=20route=20?= =?UTF-8?q?=E2=86=92=20main=20(worker=20POST=20hook)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../internal/revalidate-aggregate/route.ts | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/app/api/internal/revalidate-aggregate/route.ts diff --git a/src/app/api/internal/revalidate-aggregate/route.ts b/src/app/api/internal/revalidate-aggregate/route.ts new file mode 100644 index 00000000..4f2172ee --- /dev/null +++ b/src/app/api/internal/revalidate-aggregate/route.ts @@ -0,0 +1,40 @@ +import { timingSafeEqual } from "node:crypto"; +import { revalidateTag } from "next/cache"; +import { NextResponse } from "next/server"; + +export const runtime = "nodejs"; +export const dynamic = "force-dynamic"; + +/** + * Worker-driven CDN cache purge for the aggregate bench snapshot. + * + * The materialize worker POSTs here after successfully writing a fresh + * aggregate JSON to the VPS-served static path. This route invalidates + * the `bench-aggregate` cache tag so the next site request re-fetches + * the aggregate URL, instead of waiting for the ISR revalidate window. + * + * Auth: bearer token in `Authorization` header, `REVALIDATE_TOKEN` env, + * timing-safe compare. Fails closed when the env is missing. + */ +export async function POST(req: Request): Promise { + const secret = (process.env.REVALIDATE_TOKEN ?? "").trim(); + if (!secret) { + return NextResponse.json( + { error: "no_secret_configured" }, + { status: 503 }, + ); + } + const header = (req.headers.get("authorization") ?? "").trim(); + const expected = `Bearer ${secret}`; + const ok = + header.length === expected.length && + timingSafeEqual(Buffer.from(header), Buffer.from(expected)); + if (!ok) { + return NextResponse.json({ error: "unauthorized" }, { status: 401 }); + } + // Next 16's revalidateTag takes a required cache-profile argument. + // "default" applies the standard purge semantics for the CDN + ISR + // cache layers this route is meant to invalidate. + revalidateTag("bench-aggregate", "default"); + return NextResponse.json({ revalidated: true, tag: "bench-aggregate" }); +}