From 96092cbcb7834b799c267064a25eb0eb351b915a Mon Sep 17 00:00:00 2001 From: Agustin Kassis Date: Tue, 19 May 2026 17:34:47 -0300 Subject: [PATCH] prerender nostr project pages with 24h cache Community-only projects (no curated JSON) were missing from generateStaticParams, so URLs like /hackathons/commerce/ 404'd in production. Extend the project page + OG image generateStaticParams to include every Nostr submission visible at build time, and bump the shared submissions snapshot from cacheLife("hours") to ("days") so the cached relay round-trip lasts 24h. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../[id]/[projectId]/opengraph-image.tsx | 20 +++++++++++++--- app/hackathons/[id]/[projectId]/page.tsx | 23 ++++++++++++++++--- lib/nostrCache.ts | 2 +- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/app/hackathons/[id]/[projectId]/opengraph-image.tsx b/app/hackathons/[id]/[projectId]/opengraph-image.tsx index cc49317..3644edb 100644 --- a/app/hackathons/[id]/[projectId]/opengraph-image.tsx +++ b/app/hackathons/[id]/[projectId]/opengraph-image.tsx @@ -7,16 +7,30 @@ import { getProject, prizeForProject, } from "@/lib/hackathons"; -import { getNostrProject } from "@/lib/nostrCache"; +import { + getNostrProject, + getNostrSubmissionsSnapshot, +} from "@/lib/nostrCache"; export const alt = "Proyecto · Hackatón · La Crypta Dev"; export const size = { width: 1200, height: 630 }; export const contentType = "image/png"; -export function generateStaticParams() { - return HACKATHONS.flatMap((h) => +export async function generateStaticParams() { + const curated = HACKATHONS.flatMap((h) => getHackathonProjects(h.id).map((p) => ({ id: h.id, projectId: p.id })), ); + + const hackathonIds = new Set(HACKATHONS.map((h) => h.id)); + const seen = new Set(curated.map((p) => `${p.id}/${p.projectId}`)); + + const { projects } = await getNostrSubmissionsSnapshot(); + const community = projects + .filter((p) => p.hackathon && hackathonIds.has(p.hackathon)) + .filter((p) => !seen.has(`${p.hackathon}/${p.id}`)) + .map((p) => ({ id: p.hackathon as string, projectId: p.id })); + + return [...curated, ...community]; } const STATUS_COLOR: Record = { diff --git a/app/hackathons/[id]/[projectId]/page.tsx b/app/hackathons/[id]/[projectId]/page.tsx index 4c4c855..24a8c2e 100644 --- a/app/hackathons/[id]/[projectId]/page.tsx +++ b/app/hackathons/[id]/[projectId]/page.tsx @@ -24,17 +24,34 @@ import { import { GithubIcon } from "@/components/BrandIcons"; import { cn } from "@/lib/cn"; import { breadcrumbLd, creativeWorkLd, jsonLdScript } from "@/lib/jsonld"; -import { getNostrProject } from "@/lib/nostrCache"; +import { + getNostrProject, + getNostrSubmissionsSnapshot, +} from "@/lib/nostrCache"; import { soldierProfileHref } from "@/lib/soldierProfileLinks"; import NostrProjectServer from "./NostrProjectServer"; -export function generateStaticParams() { - return HACKATHONS.flatMap((h) => +export async function generateStaticParams() { + const curated = HACKATHONS.flatMap((h) => getHackathonProjects(h.id).map((p) => ({ id: h.id, projectId: p.id, })), ); + + const hackathonIds = new Set(HACKATHONS.map((h) => h.id)); + const seen = new Set(curated.map((p) => `${p.id}/${p.projectId}`)); + + // Prerender every Nostr-submitted project visible at build time. Without + // this, community-only projects (no curated JSON entry) 404 in production + // because the dynamic fallback never gets a chance to run. + const { projects } = await getNostrSubmissionsSnapshot(); + const community = projects + .filter((p) => p.hackathon && hackathonIds.has(p.hackathon)) + .filter((p) => !seen.has(`${p.hackathon}/${p.id}`)) + .map((p) => ({ id: p.hackathon as string, projectId: p.id })); + + return [...curated, ...community]; } function truncate(s: string, max = 155): string { diff --git a/lib/nostrCache.ts b/lib/nostrCache.ts index 46cd61d..1003fea 100644 --- a/lib/nostrCache.ts +++ b/lib/nostrCache.ts @@ -208,7 +208,7 @@ async function getSubmissionsSnapshotCached(): Promise< CachedNostrSubmissionsSnapshot > { "use cache"; - cacheLife("hours"); + cacheLife("days"); cacheTag(NOSTR_SUBMISSIONS_TAG); try { return {