import { ImageResponse } from '@vercel/og'; import { NextRequest } from 'next/server'; const fontCache = new Map(); async function loadFont(url: string): Promise { const cached = fontCache.get(url); if (cached) return cached; const res = await fetch(url); const data = await res.arrayBuffer(); fontCache.set(url, data); return data; } export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url); // Get parameters const title = searchParams.get('title') || 'Blog Post'; const description = searchParams.get('description') || ''; const tags = searchParams.get('tags')?.split(',').slice(0, 3) || []; // Load CJK font for Chinese text rendering const fontData = await loadFont( 'https://cdn.jsdelivr.net/fontsource/fonts/noto-sans-tc@latest/chinese-traditional-400-normal.woff' ); const fontBoldData = await loadFont( 'https://cdn.jsdelivr.net/fontsource/fonts/noto-sans-tc@latest/chinese-traditional-700-normal.woff' ); const imageResponse = new ImageResponse( (
{/* Header with gradient */}
個人部落格
{/* Main content */}
{/* Title */}
{title}
{/* Description */} {description && (
{description}
)} {/* Tags */} {tags.length > 0 && (
{tags.map((tag, i) => (
#{tag.trim()}
))}
)}
{/* Footer with accent line */}
gbanyan.net
), { width: 1200, height: 630, fonts: [ { name: 'Noto Sans TC', data: fontData, weight: 400 as const, style: 'normal' as const }, { name: 'Noto Sans TC', data: fontBoldData, weight: 700 as const, style: 'normal' as const }, ], } ); // Wrap response with cache headers for OG images (cache for 1 hour) return new Response(imageResponse.body, { headers: { 'Content-Type': 'image/png', 'Cache-Control': 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=86400', }, }); } catch (e: any) { console.error('Error generating OG image:', e); return new Response(`Failed to generate image: ${e.message}`, { status: 500, }); } }