From 6404dbecc8019312d7354a3b3e06f2dea2066468 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier Date: Wed, 8 Jul 2026 14:10:32 +0200 Subject: [PATCH] fix(sitemap): pre-warm HL-builder cache to avoid 100 concurrent getSpecs races Follow-up on #980. Introducing isHlBuilderSlug() inside a Promise.all over providerSlugs (~100 entries) triggered ~100 concurrent getSpecs() reads on the first prod build because the cache inside isHlBuilderSlug was still null on every callsite. That exhausted the file-descriptor pool during buildFullSitemap and made /sitemap.xml time out on the deploy-time smoke fetch: [smoke] fetching https://openchainbench.com/sitemap.xml [DOMException [AbortError]: This operation was aborted] Fix: warm the cache with a single throwaway call before the Promise.all. Subsequent calls hit the cached Set immediately. --- src/app/sitemap.ts | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts index 8f587e2a..9c772276 100644 --- a/src/app/sitemap.ts +++ b/src/app/sitemap.ts @@ -225,18 +225,24 @@ async function buildFullSitemap(): Promise { // so listing them in the sitemap pollutes it with permanent // redirects. The canonical /chains/ URLs are emitted below // by chainRoutes. + // Pre-warm the HL-builder slug cache with a single call so the + // Promise.all below doesn't race 100 concurrent getSpecs() reads + // (each awaits the same spec load before the cache initializes, + // exhausting the file-descriptor pool during buildFullSitemap and + // timing out /sitemap.xml on the deploy-time smoke fetch — observed + // 2026-07-08, DOMException [AbortError]: This operation was aborted). + // + // Tracked Hyperliquid builder frontends live under /hyperliquid/; + // /products/ 307-redirects for these slugs (products/[slug] + // /page.tsx), and the deploy-time sitemap-smoke gate rolls back on + // any 3xx. The canonical /hyperliquid/ URLs come from + // hyperliquid/[slug]/generateStaticParams via Next's automatic + // sitemap discovery, not from this file. + await isHlBuilderSlug("").catch(() => false); const validatedSlugs = ( await Promise.all( providerSlugs.map(async (slug) => { if (CHAIN_BY_SLUG.has(slug)) return null; - // Tracked Hyperliquid builder frontends live under - // /hyperliquid/; the /products/ route 307-redirects - // there for these slugs (products/[slug]/page.tsx). Emitting the - // /products/ variant advertises URLs that immediately redirect, - // and the deploy-time sitemap-smoke gate treats any 3xx as a - // rollback signal. The canonical /hyperliquid/ URLs come - // from hyperliquid/[slug]/generateStaticParams via Next's - // automatic sitemap discovery, not from this file. if (await isHlBuilderSlug(slug)) return null; const p = await getProvider(slug); return p ? slug : null;