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..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,23 +28,43 @@ 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", - }, - }); - - if (!res.ok) { - return undefined; - } - - return res.json() as Promise; - }, +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; + } +}