jamiepine/voicebox · info · Error

Failed to fetch stars

Error message

Failed to fetch stars

What it means

Thrown in the Navbar useEffect when /api/stars returns non-2xx; caught and console.erroled, leaving starCount null so the badge is hidden. /api/stars proxies getStarCount() which fetches api.github.com/repos/<repo> for stargazers_count with a 10-minute revalidate.

Source

Thrown at landing/src/components/Navbar.tsx:22

import Image from 'next/image';
import { useEffect, useState } from 'react';
import { DONATE_URL, GITHUB_REPO, TOKEN_TICKER } from '@/lib/constants';

function formatStarCount(count: number): string {
  if (count >= 1000) {
    const k = count / 1000;
    return k % 1 === 0 ? `${k}k` : `${k.toFixed(1)}k`;
  }
  return count.toString();
}

export function Navbar() {
  const [starCount, setStarCount] = useState<number | null>(null);

  useEffect(() => {
    fetch('/api/stars')
      .then((res) => {
        if (!res.ok) throw new Error('Failed to fetch stars');
        return res.json();
      })
      .then((data) => {
        if (typeof data.count === 'number') setStarCount(data.count);
      })
      .catch((error) => {
        console.error('Failed to fetch star count:', error);
      });
  }, []);

  return (
    <nav className="fixed inset-x-0 top-0 z-50 border-b border-border/50 bg-background/80 backdrop-blur-xl">
      <div className="mx-auto flex max-w-7xl items-center justify-between px-6 py-3 sm:grid sm:grid-cols-[1fr_auto_1fr] sm:gap-x-6">
        {/* Logo + wordmark */}
        <a href="/" className="flex items-center gap-2.5 justify-self-start">
          <Image
            src="/voicebox-logo-app.webp"
            alt="Voicebox"

View on GitHub (pinned to 51f49dea19)

Solutions

  1. Add a server-side GitHub token to the getStarCount() fetch to raise the rate limit.
  2. Increase the revalidate window (currently 600s) and ensure the Navbar relies on cached data.
  3. Hide the star badge gracefully when starCount is null (already the behavior).
  4. Verify GITHUB_REPO in releases.ts is correct and public.

Example fix

// before
if (!res.ok) throw new Error('Failed to fetch stars');
// after
if (!res.ok) { console.warn('stars status', res.status); return; }
Defensive patterns

Strategy: fallback

Type guard

function isStarPayload(v: unknown): v is { count: number } {
  return typeof v === 'object' && v !== null && typeof (v as { count?: unknown }).count === 'number';
}

Try / catch

try {
  const res = await fetch('/api/stars');
  if (!res.ok) throw new Error('Failed to fetch stars');
  const data = await res.json();
  if (typeof data.count === 'number') setStarCount(data.count);
} catch (error) {
  console.error('Failed to fetch star count:', error);
  // starCount stays null; badge hidden
}

Prevention

When it happens

Trigger: GET /api/stars returns 500 because getStarCount() threw (GitHub 403/429 rate limit, repo not found, network). Every page rendering the Navbar triggers the fetch, multiplying rate-limit pressure.

Common situations: Unauthenticated GitHub rate limit (60/hr) exhausted because the Navbar mounts on every landing route. GITHUB_REPO pointing at a renamed/private repo. Deploy IP blocked by GitHub abuse detection.

Related errors


AI-assisted analysis of jamiepine/voicebox@51f49dea19 (2026-08-12). Data as JSON: /api/errors/3427a597a15c0eee. Report an issue: GitHub.