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 {