SPB Git

Repository Public

Commit

web: /data rendered on demand with a 1 h runtime cache (build-time prerender exceeded 240 s); worker: min ask only from the representative variant

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Simon-Pierre Boucher committed · Sep 11, 2026 parent 38928e5
2 changed files +7 −3 Browse files
modified apps/web/src/app/data/page.tsx +6 −2
@@ -1,4 +1,5 @@
1 import type { Metadata } from 'next'; 1 import type { Metadata } from 'next';
2+import { unstable_cache } from 'next/cache';
2 3
3 import Link from 'next/link'; 4 import Link from 'next/link';
4 import { readFile } from 'node:fs/promises'; 5 import { readFile } from 'node:fs/promises';
@@ -13,7 +14,10 @@ import { catName, humanize } from '@/lib/taxonomy';
13 14
14 export const metadata: Metadata = { title: 'Data & methodology', description: 'Coverage by category, source and connector; RareIndex methodology; exports and API access.' }; 15 export const metadata: Metadata = { title: 'Data & methodology', description: 'Coverage by category, source and connector; RareIndex methodology; exports and API access.' };
15 // Coverage aggregates over millions of rows take ~70 s: prerender at build (staticPageGenerationTimeout raised), then hourly ISR. 16 // Coverage aggregates over millions of rows take ~70 s: prerender at build (staticPageGenerationTimeout raised), then hourly ISR.
16−export const revalidate = 3600; 17+// Rendered on demand and cached for an hour at runtime: the coverage aggregates run over millions of
18+// rows and exceeded the 240 s prerender budget at build time, which broke deployments.
19+export const dynamic = 'force-dynamic';
20+const loadDataPage = unstable_cache(async () => Promise.all([getSiteStats(), getConnectorCoverage(), getFxCoverage(), getMarketRows()]), ['data-page-v1'], { revalidate: 3600 });
17 21
18 async function loadMethodology(): Promise<string> { 22 async function loadMethodology(): Promise<string> {
19 const candidates = [path.resolve(process.cwd(), '../../docs/METHODOLOGY.md'), path.resolve(process.cwd(), 'docs/METHODOLOGY.md'), path.resolve(process.cwd(), '../docs/METHODOLOGY.md')]; 23 const candidates = [path.resolve(process.cwd(), '../../docs/METHODOLOGY.md'), path.resolve(process.cwd(), 'docs/METHODOLOGY.md'), path.resolve(process.cwd(), '../docs/METHODOLOGY.md')];
@@ -49,7 +53,7 @@ function inline(s: string): string {
49 } 53 }
50 54
51 export default async function DataPage() { 55 export default async function DataPage() {
52− const [stats, connectors, fx, rows, md] = await Promise.all([getSiteStats(), getConnectorCoverage(), getFxCoverage(), getMarketRows(), loadMethodology()]); 56+ const [[stats, connectors, fx, rows], md] = await Promise.all([loadDataPage(), loadMethodology()]);
53 const active = rows.filter((r) => r.counts.assets > 0); 57 const active = rows.filter((r) => r.counts.assets > 0);
54 return ( 58 return (
55 <div> 59 <div>
modified workers/valuation/run.ts +1 −1
@@ -231,7 +231,7 @@ export async function valueAsset(assetId: string, opts: { now?: Date; rebuildHis
231 volume30dUsd: sales30d ? validSales.filter((s) => now.getTime() - s.saleDate.getTime() <= 30 * DAY).reduce((a, s) => a + Number(s.priceUsd), 0) : null, 231 volume30dUsd: sales30d ? validSales.filter((s) => now.getTime() - s.saleDate.getTime() <= 30 * DAY).reduce((a, s) => a + Number(s.priceUsd), 0) : null,
232 activeListings: activeAll.count, 232 activeListings: activeAll.count,
233 // lowest ask of the representative variant (comparable to the headline RIV); all variants otherwise 233 // lowest ask of the representative variant (comparable to the headline RIV); all variants otherwise
234− minAskUsd: repActive?.minAsk ?? activeAll.minAsk, 234+ minAskUsd: repActive?.minAsk ?? (rep ? null : activeAll.minAsk), // never a graded slab's ask next to a raw RIV
235 observationsCount: obsCount?.n ?? 0, 235 observationsCount: obsCount?.n ?? 0,
236 sourcesCount, 236 sourcesCount,
237 liquidityScore: liq, 237 liquidityScore: liq,
238 238
Menu

Theme