@@ -3,19 +3,36 @@ import { NextRequest } from "next/server"
33
44export 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 ( / s r c : u r l \( ( .+ ) \) f o r m a t \( ' ( o p e n t y p e | t r u e t y p e ) ' \) / )
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 ( / s r c : \s * u r l \( ( [ ^ ) ] + ) \) \s * f o r m a t \( ' (?: w o f f 2 | w o f f | o p e n t y p e | t r u e t y p e ) ' \) / i) ||
25+ css . match ( / u r l \( ( [ ^ ) ] + ) \) / 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
2138export 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