import { ImageResponse } from "next/og"; import type { ImageResponseOptions, NextRequest } from "next/server"; import { readFile } from "node:fs/promises"; import { join } from "node:path"; export const runtime = "nodejs"; const size = { width: 1200, height: 630, }; let fontsCache: { geistSemiBold: Buffer; geistRegular: Buffer; geistMedium: Buffer; geistMono: Buffer; } | null = null; async function loadFonts() { if (fontsCache) return fontsCache; const [geistSemiBold, geistRegular, geistMedium, geistMono] = await Promise.all([ readFile(join(process.cwd(), "assets/Geist-SemiBold.ttf")), readFile(join(process.cwd(), "assets/Geist-Regular.ttf")), readFile(join(process.cwd(), "assets/Geist-Medium.ttf")), readFile(join(process.cwd(), "assets/GeistMono-Regular.ttf")), ]); fontsCache = { geistSemiBold, geistRegular, geistMedium, geistMono }; return fontsCache; } export async function GET(request: NextRequest) { const searchParams = request.nextUrl.searchParams; const title = (searchParams.get("title") ?? "Documentation").slice(0, 100); const description = searchParams.get("description")?.slice(0, 93) ?? null; const variant = searchParams.get("variant"); if (variant && !["home", "page"].includes(variant)) { return new Response("Invalid variant", { status: 400 }); } let fonts: Awaited> | null = null; try { fonts = await loadFonts(); } catch (error) { // Don't fail the OG endpoint if fonts are missing (e.g. serverless file tracing). // We'll fall back to system fonts so previews still work. console.error("Failed to load fonts for OG image:", error); } const fontSans = fonts ? "Geist" : "sans-serif"; const fontMono = fonts ? "GeistMono" : "monospace"; const homeContent = (
assistant-ui
An open-source React toolkit for production AI chat experiences
); const pageContent = (
assistant-ui
assistant-ui.com
50 ? 56 : title.length > 30 ? 68 : 80, fontWeight: 600, color: "#ffffff", fontFamily: fontSans, lineHeight: 1.1, letterSpacing: "-0.02em", }} > {title} {description && ( {description.length > 90 ? `${description.slice(0, 90)}...` : description} )}
); try { const imageOptions: ImageResponseOptions = { ...size, headers: { "Cache-Control": "public, max-age=86400, s-maxage=31536000", }, }; if (fonts) { imageOptions.fonts = [ { name: "Geist", data: fonts.geistSemiBold, style: "normal", weight: 600, }, { name: "Geist", data: fonts.geistRegular, style: "normal", weight: 400, }, { name: "Geist", data: fonts.geistMedium, style: "normal", weight: 500, }, { name: "GeistMono", data: fonts.geistMono, style: "normal", weight: 400, }, ]; } return new ImageResponse( variant === "home" ? homeContent : pageContent, imageOptions, ); } catch (error) { console.error("Failed to generate OG image:", error); return new Response("Failed to generate image", { status: 500 }); } }