Skip to content

bug: OG Image Route Fetches SVG Endpoint But Attempts JSON Parse — Always Shows Zeros #8267

Description

@ionfwsrijan

Description

The Open Graph image generator at app/api/og/route.tsx fetches /api/streak?user=...&refresh=true and then calls res.json() on the response (line 24). However, the /api/streak endpoint (app/api/streak/route.ts:130) returns an image/svg+xml response, not JSON. The res.json() call always throws a parse error, which is silently swallowed by the empty catch {} block on line 34. The fallback values (totalCommits = 0, longestStreak = 0, currentStreak = 0) are always used, meaning every OG preview image shows 0 / 0 / 0 regardless of the user's actual stats.

This means any time a user shares their CommitPulse profile link on social media, Discord, Slack, or any platform that renders OG previews, the generated image displays completely empty/zeroed stats — defeating the core purpose of the OG image.

Affected Files

  • app/api/og/route.tsx (lines 19–36 — fetch + JSON parse of SVG response)
  • app/api/streak/route.ts (line 130 — returns Content-Type: image/svg+xml)

Expected Behaviour

The OG image should display the user's real contribution stats (total commits, longest streak, current streak) by correctly obtaining the data from the GitHub API.

Actual Behaviour

The OG image always shows 0 for all three stats because the SVG response body cannot be parsed as JSON. The empty catch {} block silently swallows the parse error, and the zero-initialized variables are used to render the image. Every shared profile link generates a meaningless OG preview.

Proposed Fix

Replace the self-referencing HTTP fetch to the SVG-producing API route with direct imports of the data-fetching functions. Both fetchGitHubContributions and calculateStreak work in the edge runtime, so they can be called directly:

// app/api/og/route.tsx — complete replacement of the data-fetching logic

import { ImageResponse } from 'next/og';
import { NextRequest } from 'next/server';
import { ogParamsSchema } from '../../../lib/validations';
import { fetchGitHubContributions } from '../../../lib/github';
import { calculateStreak } from '../../../lib/calculate';

export const runtime = 'edge';

export async function GET(req: NextRequest) {
  const { searchParams } = new URL(req.url);
  const { user } = ogParamsSchema.parse(
    Object.fromEntries(searchParams.entries())
  );

  let totalCommits = 0;
  let longestStreak = 0;
  let currentStreak = 0;

  try {
    // Call the GitHub data layer directly instead of fetching the
    // SVG-producing /api/streak route (which returns image/svg+xml,
    // not JSON).
    const calendar = await fetchGitHubContributions(user, {
      bypassCache: true,
    });
    const stats = calculateStreak(calendar);
    totalCommits = stats.totalContributions;
    longestStreak = stats.longestStreak;
    currentStreak = stats.currentStreak;
  } catch {
    // fallback — show zeros if the user doesn't exist or the API is down
  }

  return new ImageResponse(
    // ... rest of the JSX stays exactly the same ...

Key changes:

  1. Removed the fetch(\${baseUrl}/api/streak?user=...`)` call that returned SVG
  2. Imported fetchGitHubContributions from lib/github and calculateStreak from lib/calculate
  3. Called the data functions directly, which return structured data instead of SVG markup
  4. The OG image now correctly displays real contribution statistics

An alternative fix if direct imports aren't desired (e.g., to keep the OG route decoupled) would be to add a separate /api/streak/data endpoint that returns JSON, and have the OG route fetch that instead. However, direct imports are simpler, faster (no HTTP overhead), and avoid the coupling issue entirely.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions