Skip to content

Commit dd0bbcd

Browse files
committed
fix(web): make OG image endpoint resilient to Google Fonts outages
1 parent 6b4de52 commit dd0bbcd

File tree

1 file changed

+40
-25
lines changed

1 file changed

+40
-25
lines changed

apps/web-roo-code/src/app/api/og/route.tsx

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,36 @@ import { NextRequest } from "next/server"
33

44
export const runtime = "edge"
55

6-
async function loadGoogleFont(font: string, text: string) {
7-
const url = `https://fonts.googleapis.com/css2?family=${font}&text=${encodeURIComponent(text)}`
8-
const css = await (await fetch(url)).text()
9-
const resource = css.match(/src: url\((.+)\) format\('(opentype|truetype)'\)/)
10-
11-
if (resource && resource[1]) {
12-
const response = await fetch(resource[1])
13-
if (response.status === 200) {
14-
return await response.arrayBuffer()
15-
}
6+
async function fetchWithTimeout(url: string, init?: RequestInit, timeoutMs = 3000) {
7+
const controller = new AbortController()
8+
const id = setTimeout(() => controller.abort(), timeoutMs)
9+
try {
10+
return await fetch(url, { ...init, signal: controller.signal })
11+
} finally {
12+
clearTimeout(id)
1613
}
14+
}
15+
16+
async function loadGoogleFont(font: string, text: string): Promise<ArrayBuffer | null> {
17+
try {
18+
const url = `https://fonts.googleapis.com/css2?family=${font}&text=${encodeURIComponent(text)}`
19+
const cssRes = await fetchWithTimeout(url)
20+
if (!cssRes.ok) return null
21+
const css = await cssRes.text()
22+
23+
const match =
24+
css.match(/src:\s*url\(([^)]+)\)\s*format\('(?:woff2|woff|opentype|truetype)'\)/i) ||
25+
css.match(/url\(([^)]+)\)/i)
26+
27+
const fontUrl = match && match[1] ? match[1].replace(/^['"]|['"]$/g, "") : null
28+
if (!fontUrl) return null
1729

18-
throw new Error("failed to load font data")
30+
const res = await fetchWithTimeout(fontUrl, undefined, 5000)
31+
if (!res.ok) return null
32+
return await res.arrayBuffer()
33+
} catch {
34+
return null
35+
}
1936
}
2037

2138
export async function GET(request: NextRequest) {
@@ -38,6 +55,17 @@ export async function GET(request: NextRequest) {
3855
const variant = title.length % 2 === 0 ? "a" : "b"
3956
const backgroundUrl = `${baseUrl}/og/base_${variant}.png`
4057

58+
// Preload fonts with graceful fallbacks
59+
const regularFont = await loadGoogleFont("Inter", displayText)
60+
const boldFont = await loadGoogleFont("Inter:wght@700", displayText)
61+
const fonts: { name: string; data: ArrayBuffer; style: "normal" | "italic"; weight: number }[] = []
62+
if (regularFont) {
63+
fonts.push({ name: "Inter", data: regularFont, style: "normal", weight: 400 })
64+
}
65+
if (boldFont) {
66+
fonts.push({ name: "Inter", data: boldFont, style: "normal", weight: 700 })
67+
}
68+
4169
return new ImageResponse(
4270
(
4371
<div
@@ -124,20 +152,7 @@ export async function GET(request: NextRequest) {
124152
{
125153
width: 1200,
126154
height: 630,
127-
fonts: [
128-
{
129-
name: "Inter",
130-
data: await loadGoogleFont("Inter", displayText),
131-
style: "normal",
132-
weight: 400,
133-
},
134-
{
135-
name: "Inter",
136-
data: await loadGoogleFont("Inter:wght@700", displayText),
137-
style: "normal",
138-
weight: 700,
139-
},
140-
],
155+
fonts: fonts.length ? fonts : undefined,
141156
// Cache for 7 days in production, 3 seconds in development
142157
headers: {
143158
"Cache-Control":

0 commit comments

Comments
 (0)