From e8cff279873983553c857e5f39ae86d695b45c14 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2026 06:40:56 +0000 Subject: [PATCH] perf: deduplicate concurrent GitHub API requests Co-authored-by: is0692vs <135803462+is0692vs@users.noreply.github.com> --- src/app/api/og/[username]/route.test.ts | 20 ++++++++++++++ src/app/api/og/[username]/route.tsx | 36 ++++++++++++++++++------- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src/app/api/og/[username]/route.test.ts b/src/app/api/og/[username]/route.test.ts index edabdca5..205a79b5 100644 --- a/src/app/api/og/[username]/route.test.ts +++ b/src/app/api/og/[username]/route.test.ts @@ -149,6 +149,26 @@ describe("OG Image Route", () => { expect(res.status).toBe(429); expect(res.headers.get("Retry-After")).toBe("0"); }); + + it("should dedupe concurrent requests", async () => { + const mockFetch = vi.spyOn(global, "fetch").mockImplementation(() => { + return new Promise((resolve) => { + setTimeout(() => { + resolve(new Response(JSON.stringify({ name: "Valid User" }), { status: 200 })); + }, 50); + }); + }); + + const req1 = new NextRequest("http://localhost/api/og/dedupetest"); + const req2 = new NextRequest("http://localhost/api/og/dedupetest"); + + await Promise.all([ + GET(req1, { params: Promise.resolve({ username: "dedupetest" }) }), + GET(req2, { params: Promise.resolve({ username: "dedupetest" }) }) + ]); + + expect(mockFetch).toHaveBeenCalledTimes(1); + }); }); function collectText(node: unknown): string[] { diff --git a/src/app/api/og/[username]/route.tsx b/src/app/api/og/[username]/route.tsx index 0223dd1d..54a30d06 100644 --- a/src/app/api/og/[username]/route.tsx +++ b/src/app/api/og/[username]/route.tsx @@ -7,6 +7,9 @@ import { RateLimiter } from "@/lib/rateLimit"; import { getClientIp } from "@/lib/rateLimit"; export const runtime = "edge"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const inflightRequests = new Map>(); const rateLimiter = new RateLimiter(50, 60 * 1000); const ONE_HOUR_IN_SECONDS = 60 * 60; const ONE_DAY_IN_SECONDS = 24 * ONE_HOUR_IN_SECONDS; @@ -42,15 +45,30 @@ export async function GET( let publicRepos = 0; try { - const res = await fetch(`https://api.github.com/users/${encodeURIComponent(username)}`, { - headers: { - Accept: "application/vnd.github.v3+json", - "User-Agent": "github-user-summary", - }, - next: { revalidate: ONE_DAY_IN_SECONDS }, - }); - if (res.ok) { - const data = await res.json(); + let fetchPromise = inflightRequests.get(username); + if (!fetchPromise) { + fetchPromise = (async () => { + try { + const res = await fetch(`https://api.github.com/users/${encodeURIComponent(username)}`, { + headers: { + Accept: "application/vnd.github.v3+json", + "User-Agent": "github-user-summary", + }, + next: { revalidate: ONE_DAY_IN_SECONDS }, + }); + if (res.ok) { + return await res.json(); + } + return null; + } finally { + inflightRequests.delete(username); + } + })(); + inflightRequests.set(username, fetchPromise); + } + + const data = await fetchPromise; + if (data) { name = data.name ?? username; bio = data.bio ?? ""; avatarUrl = data.avatar_url ?? "";