From b99282080dcffe0b7ef48a981313f0d7c813d4d8 Mon Sep 17 00:00:00 2001 From: Firekeeper Date: Mon, 31 Aug 2026 21:39:32 +0700 Subject: [PATCH 1/2] chore(dashboard): move chain SEO fetch to seo-pages.thirdweb.xyz Also guard the fetch: SEO copy is decorative, so a transport failure should degrade the page rather than throw. Co-Authored-By: Claude Opus 5 --- .../[chain_id]/(chainPage)/apis/chain-seo.ts | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/apis/chain-seo.ts b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/apis/chain-seo.ts index c4bc72c873e..abcbd8c0b5e 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/apis/chain-seo.ts +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/apis/chain-seo.ts @@ -30,20 +30,24 @@ type ChainSeo = { export const fetchChainSeo = unstable_cache( async (chainId: number) => { - const url = new URL( - `https://seo-pages-generator-5814.zeet-nftlabs.zeet.app/chain/${chainId}`, - ); - const res = await fetch(url, { - headers: { - "Content-Type": "application/json", - }, - }); + const url = new URL(`https://seo-pages.thirdweb.xyz/chain/${chainId}`); - if (!res.ok) { + // SEO copy is decorative -- never let a fetch failure take down the page. + try { + const res = await fetch(url, { + headers: { + "Content-Type": "application/json", + }, + }); + + if (!res.ok) { + return undefined; + } + + return (await res.json()) as ChainSeo; + } catch { return undefined; } - - return res.json() as Promise; }, ["chain-seo"], { revalidate: 60 * 60 * 24 }, // 24 hours From 9a256432a2d3987ecc2caa86edde583efd776505 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Mon, 31 Aug 2026 23:46:02 +0700 Subject: [PATCH 2/2] chore(dashboard): don't cache transient chain-seo failures Split the cached fetch from the fallback: a 4xx (no SEO entry) is cached, but a 5xx or network error throws so unstable_cache doesn't persist a transient failure for 24h. Page still degrades to undefined. Co-Authored-By: Claude Fable 5 --- .../[chain_id]/(chainPage)/apis/chain-seo.ts | 58 ++++++++++++------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/apis/chain-seo.ts b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/apis/chain-seo.ts index abcbd8c0b5e..54a6ca9a818 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/apis/chain-seo.ts +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/apis/chain-seo.ts @@ -28,27 +28,43 @@ type ChainSeo = { }; }; -export const fetchChainSeo = unstable_cache( - async (chainId: number) => { - const url = new URL(`https://seo-pages.thirdweb.xyz/chain/${chainId}`); - - // SEO copy is decorative -- never let a fetch failure take down the page. - try { - const res = await fetch(url, { - headers: { - "Content-Type": "application/json", - }, - }); - - if (!res.ok) { - return undefined; - } - - return (await res.json()) as ChainSeo; - } catch { - return undefined; - } - }, +async function fetchChainSeoUncached( + chainId: number, +): Promise { + const url = new URL(`https://seo-pages.thirdweb.xyz/chain/${chainId}`); + + const res = await fetch(url, { + headers: { "Content-Type": "application/json" }, + }); + + // 4xx means this chain simply has no SEO entry -- a stable answer worth caching. + if (res.status >= 400 && res.status < 500) { + return undefined; + } + + // 5xx (or any other non-OK) is transient. Throw so unstable_cache does not + // persist the failure and starve the page of SEO for a full day. + if (!res.ok) { + throw new Error(`chain SEO fetch failed: ${res.status}`); + } + + return (await res.json()) as ChainSeo; +} + +const fetchChainSeoCached = unstable_cache( + fetchChainSeoUncached, ["chain-seo"], { revalidate: 60 * 60 * 24 }, // 24 hours ); + +export async function fetchChainSeo( + chainId: number, +): Promise { + // SEO copy is decorative: a failure must never take the page down. A transient + // error propagates out of the cache uncached, and we degrade to undefined here. + try { + return await fetchChainSeoCached(chainId); + } catch { + return undefined; + } +}