@@ -7,12 +7,24 @@ import satori from "satori";
77
88export const prerender = false ;
99
10- export const GET : APIRoute = async ( { url } ) : Promise < Response > => {
10+
11+
12+ export const GET : APIRoute = async ( { url, request } ) : Promise < Response > => {
13+ const searchParams = new URL ( url ) . searchParams ;
14+ const title = searchParams . get ( 'title' ) || 'Piyush Mehta' ;
15+ const description = searchParams . get ( 'description' ) || 'Software Engineer & Tech Speaker' ;
16+ const template = searchParams . get ( 'template' ) || 'modern' ;
17+
1118 try {
12- const searchParams = new URL ( url ) . searchParams ;
13- const title = searchParams . get ( 'title' ) || 'Piyush Mehta' ;
14- const description = searchParams . get ( 'description' ) || 'Software Engineer & Tech Speaker' ;
15- const template = searchParams . get ( 'template' ) || 'modern' ;
19+
20+ // Check if this is a social media crawler
21+ const userAgent = request . headers . get ( 'user-agent' ) || '' ;
22+ const isSocialCrawler = / f a c e b o o k | t w i t t e r | l i n k e d i n | w h a t s a p p | t e l e g r a m | d i s c o r d | s l a c k / i. test ( userAgent ) ;
23+
24+ // For social media crawlers, add extra delay to ensure proper rendering
25+ if ( isSocialCrawler ) {
26+ await new Promise ( resolve => setTimeout ( resolve , 100 ) ) ;
27+ }
1628
1729 // Load fonts
1830 const fontPath = join ( process . cwd ( ) , "InterVariable.ttf" ) ;
@@ -257,15 +269,75 @@ export const GET: APIRoute = async ({ url }): Promise<Response> => {
257269 const pngData = resvg . render ( ) ;
258270 const pngBuffer = pngData . asPng ( ) ;
259271
272+ // Different caching strategies for social crawlers vs regular users
273+ const cacheControl = isSocialCrawler
274+ ? 'public, max-age=86400, s-maxage=86400' // 24 hours for crawlers
275+ : 'public, max-age=31536000, immutable' ; // 1 year for regular users
276+
260277 return new Response ( new Uint8Array ( pngBuffer ) , {
261278 headers : {
262279 'Content-Type' : 'image/png' ,
263280 'Content-Length' : pngBuffer . length . toString ( ) ,
264- 'Cache-Control' : 'public, max-age=31536000, immutable' ,
281+ 'Cache-Control' : cacheControl ,
282+ 'Access-Control-Allow-Origin' : '*' ,
283+ 'Access-Control-Allow-Methods' : 'GET' ,
284+ 'Access-Control-Allow-Headers' : 'Content-Type' ,
285+ 'X-Robots-Tag' : 'noindex' ,
286+ // Additional headers for social media crawlers
287+ ...( isSocialCrawler && {
288+ 'X-Crawler-Friendly' : 'true' ,
289+ 'Link' : `<${ url } >; rel="canonical"` ,
290+ } ) ,
265291 } ,
266292 } ) ;
267293 } catch ( error ) {
268294 console . error ( 'Error generating OG image:' , error ) ;
269- return new Response ( 'Error generating OG image' , { status : 500 } ) ;
295+
296+ // Return a fallback static image for social media crawlers
297+ try {
298+ const fallbackImagePath = join ( process . cwd ( ) , "public/images/social.jpg" ) ;
299+ const fallbackImage = fs . readFileSync ( fallbackImagePath ) ;
300+
301+ return new Response ( fallbackImage , {
302+ headers : {
303+ 'Content-Type' : 'image/jpeg' ,
304+ 'Content-Length' : fallbackImage . length . toString ( ) ,
305+ 'Cache-Control' : 'public, max-age=86400' ,
306+ 'Access-Control-Allow-Origin' : '*' ,
307+ 'Access-Control-Allow-Methods' : 'GET' ,
308+ 'Access-Control-Allow-Headers' : 'Content-Type' ,
309+ 'X-Fallback-Image' : 'true' ,
310+ } ,
311+ } ) ;
312+ } catch ( fallbackError ) {
313+ console . error ( 'Fallback image error:' , fallbackError ) ;
314+
315+ // Create a simple fallback SVG image as last resort
316+ const fallbackSvg = `
317+ <svg width="1200" height="630" xmlns="http://www.w3.org/2000/svg">
318+ <rect width="1200" height="630" fill="#1f2347"/>
319+ <text x="600" y="300" text-anchor="middle" fill="#f9fafb" font-family="Arial" font-size="48" font-weight="bold">
320+ ${ title ? title . substring ( 0 , 50 ) + ( title . length > 50 ? '...' : '' ) : 'Piyush Mehta' }
321+ </text>
322+ <text x="600" y="360" text-anchor="middle" fill="#d1d5db" font-family="Arial" font-size="24">
323+ piyushmehta.com
324+ </text>
325+ </svg>
326+ ` ;
327+
328+ const resvg = new Resvg ( fallbackSvg ) ;
329+ const pngData = resvg . render ( ) ;
330+ const pngBuffer = pngData . asPng ( ) ;
331+
332+ return new Response ( new Uint8Array ( pngBuffer ) , {
333+ headers : {
334+ 'Content-Type' : 'image/png' ,
335+ 'Content-Length' : pngBuffer . length . toString ( ) ,
336+ 'Cache-Control' : 'public, max-age=3600' , // 1 hour cache for error fallbacks
337+ 'Access-Control-Allow-Origin' : '*' ,
338+ 'X-Emergency-Fallback' : 'true' ,
339+ } ,
340+ } ) ;
341+ }
270342 }
271343} ;
0 commit comments