{"record":{"id":"8b1e0598cc688919","repo":"jamiepine/voicebox","slug":"failed-to-fetch-releases-8b1e05","errorCode":null,"errorMessage":"Failed to fetch releases","messagePattern":"Failed to fetch releases","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"info","filePath":"landing/src/app/page.tsx","lineNumber":28,"sourceCode":"import {Footer} from \"@/components/Footer\";\nimport {Navbar} from \"@/components/Navbar\";\nimport {Personalities} from \"@/components/Personalities\";\nimport {AppleIcon, LinuxIcon, WindowsIcon} from \"@/components/PlatformIcons\";\nimport {SupportedModels} from \"@/components/SupportedModels\";\nimport {Testimonials} from \"@/components/Testimonials\";\nimport {TokenTeaser} from \"@/components/TokenTeaser\";\nimport {TutorialsSection} from \"@/components/TutorialsSection\";\nimport {VoiceCreator} from \"@/components/VoiceCreator\";\nimport {GITHUB_REPO} from \"@/lib/constants\";\n\nexport default function Home() {\n\tconst [version, setVersion] = useState<string | null>(null);\n\tconst [totalDownloads, setTotalDownloads] = useState<number | null>(null);\n\n\tuseEffect(() => {\n\t\tfetch(\"/api/releases\")\n\t\t\t.then((res) => {\n\t\t\t\tif (!res.ok) throw new Error(\"Failed to fetch releases\");\n\t\t\t\treturn res.json();\n\t\t\t})\n\t\t\t.then((data) => {\n\t\t\t\tif (data.version) setVersion(data.version);\n\t\t\t\tif (data.totalDownloads != null) setTotalDownloads(data.totalDownloads);\n\t\t\t})\n\t\t\t.catch((error) => {\n\t\t\t\tconsole.error(\"Failed to fetch release info:\", error);\n\t\t\t});\n\t}, []);\n\n\treturn (\n\t\t<>\n\t\t\t<Navbar />\n\n\t\t\t{/* ── Hero Section ─────────────────────────────────────────────── */}\n\t\t\t<section className=\"relative pt-32 pb-16\">\n\t\t\t\t{/* Background glow */}","sourceCodeStart":10,"sourceCodeEnd":46,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/landing/src/app/page.tsx#L10-L46","documentation":"Thrown in the home page useEffect when /api/releases returns non-2xx; caught and console.erroled, leaving version/totalDownloads null. Identical mechanism to the /capture page release fetch — same /api/releases route proxying GitHub.","triggerScenarios":"GET /api/releases returns 500 because getLatestRelease() failed upstream (GitHub 403/429, network, misconfigured GITHUB_REPO).","commonSituations":"Shared deploy IP exhausts GitHub's unauthenticated rate limit across multiple landing routes all hitting /api/releases. Preview/branch deploys with blocked egress.","solutions":["Share one cached release fetch across pages (the in-memory cache in releases.ts already helps; ensure pages hit the same cache).","Add a server-side GitHub token to lift the rate limit.","Render the version badge conditionally so null doesn't break layout.","Confirm GITHUB_REPO is the intended public repo."],"exampleFix":"// before\nif (!res.ok) throw new Error('Failed to fetch releases');\n// after\nif (!res.ok) { console.warn('releases status', res.status); return; }","handlingStrategy":"fallback","validationCode":null,"typeGuard":"function isReleasePayload(v: unknown): v is { version?: string; totalDownloads?: number } {\n  return typeof v === 'object' && v !== null;\n}","tryCatchPattern":"try {\n  const res = await fetch('/api/releases');\n  if (!res.ok) throw new Error('Failed to fetch releases');\n  const data = await res.json();\n  if (data.version) setVersion(data.version);\n} catch (error) {\n  console.error('Failed to fetch release info:', error);\n}","preventionTips":["Rely on the shared in-memory cache in releases.ts across pages to limit requests.","Render the version badge only when version is non-null.","Add a server-side GitHub token to lift the unauthenticated cap."],"tags":["network","external-api","github","nextjs","releases","landing"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}