From b0758fb90cb3aa8e2b1858a0610de8453f03a8c5 Mon Sep 17 00:00:00 2001 From: Flotapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Wed, 26 Aug 2026 20:20:32 +0200 Subject: [PATCH] fix: sitemap aggregate timeout + CDN caching (#2090) * feat: shareable wallet URLs + HL fee/funding breakdown - /fee-compare/[venueA]/[venueB]/[wallet] dynamic route auto-submits on load - pushState updates URL after analyze so any comparison is shareable - FeeCompareClient accepts initialVenueA/B/Wallet/Days props - HL wallet side: replace 2-cell grid with inline fee/funding/net breakdown * fix: remove 'No activity found' text from crossSim projection side * fix: sitemap aggregate timeout + CDN caching (s-maxage stripped by dynamic route) --- src/app/api/aggregate/route.ts | 15 +++++++++++++-- src/lib/aggregate-blob.ts | 6 +++++- src/lib/sitemap-builder.ts | 11 +++++++---- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/app/api/aggregate/route.ts b/src/app/api/aggregate/route.ts index 58b769cb..871c96fa 100644 --- a/src/app/api/aggregate/route.ts +++ b/src/app/api/aggregate/route.ts @@ -11,9 +11,17 @@ // of kv.openchainbench.com to get near-zero latency from any Vercel // function in the same region. export const runtime = "nodejs"; +// ISR: generate once, serve from Vercel Data Cache for 60 s, regenerate +// in background. Keeps s-maxage in the response (Next.js strips it when +// the route uses cache:"no-store" fetches internally, which makes the +// route dynamic and prevents CDN caching — removing that flag fixes it). +export const revalidate = 60; const UPSTREAM = "https://kv.openchainbench.com/aggregate/latest.json"; -const UPSTREAM_TIMEOUT_MS = 25_000; +// Paris VPS → IAD1 with 7.3 MB payload: measured 37-49 s uncompressed. +// With Accept-Encoding:gzip Caddy compresses to ~2.3 MB in ~18 s. Set +// the timeout to 70 s so cold-cache first requests always complete. +const UPSTREAM_TIMEOUT_MS = 70_000; export async function GET() { // Retry once: some Vercel function instances can't reach the Paris VPS @@ -28,7 +36,10 @@ export async function GET() { upstream = await fetch(UPSTREAM, { signal: AbortSignal.timeout(UPSTREAM_TIMEOUT_MS), headers: { "Accept-Encoding": "gzip, br" }, - cache: "no-store", + // No cache:"no-store" here: that flag opts the route into + // dynamic mode and causes Next.js to strip s-maxage from our + // response headers, breaking Vercel CDN caching. The ISR + // revalidate=60 above handles freshness instead. }); } catch (err) { lastErr = String(err); diff --git a/src/lib/aggregate-blob.ts b/src/lib/aggregate-blob.ts index c0017111..0c2ff32e 100644 --- a/src/lib/aggregate-blob.ts +++ b/src/lib/aggregate-blob.ts @@ -35,7 +35,11 @@ const DEFAULT_URL = process.env.VERCEL_ENV ? "https://openchainbench.com/api/aggregate" : "https://kv.openchainbench.com/aggregate/latest.json"; -const FETCH_TIMEOUT_MS = 20_000; +// Needs to be > the Paris VPS response time (measured 18 s compressed, +// 37-49 s uncompressed). Set to 65 s so cold-cache ISR misses on +// /api/aggregate have enough headroom to complete before we give up and +// fall through to the Redis fan-out path. +const FETCH_TIMEOUT_MS = 65_000; const MIN_BENCHES = 40; type AggregateEnvelope = { diff --git a/src/lib/sitemap-builder.ts b/src/lib/sitemap-builder.ts index 669fae04..84664741 100644 --- a/src/lib/sitemap-builder.ts +++ b/src/lib/sitemap-builder.ts @@ -577,11 +577,14 @@ async function buildFullSitemap(): Promise { export async function buildSitemap(): Promise { try { - // 20 s: tight enough to reply well within the smoke-test's 90 s window - // even if the static fallback itself takes a few seconds. The Vercel - // maxDuration on the route is 60 s; this leaves 40 s of headroom. + // 55 s: the Vercel maxDuration on the route is 60 s. With /api/aggregate + // ISR-cached (revalidate=60 on that route) warm requests return in <1 ms + // and the full sitemap completes in ~10 s. Cold-start requests (first hit + // after deployment) may take 18-49 s for the aggregate; 55 s gives enough + // headroom while leaving 5 s for buildStaticFallback() before Vercel kills + // the function. const timeout = new Promise((_, reject) => - setTimeout(() => reject(new Error("sitemap build timeout")), 20_000), + setTimeout(() => reject(new Error("sitemap build timeout")), 55_000), ); // Attach a no-op .catch() to prevent unhandled-rejection crashes if // buildFullSitemap() rejects AFTER the race has already settled (via