Dynamic OG image

/api/og generates an SVG per request, keyed by query string. Linking it from <meta property="og:image"> lets every page emit its own preview without committing 100 PNG variants. SVG is the smallest path; production apps that need raster output run SVG → resvg-js downstream.

Tip
Social crawlers (Facebook, Twitter/X, LinkedIn) don't follow redirects on og:image — the URL must respond directly with 200. SVG is rejected by most crawlers (they expect image/png or image/jpeg): use bext's built-in rasteriser at /__bext/api/og to serve a ready-made PNG.

Live preview

generated OG preview

/api/og?title=Hello%2C%20OG&subtitle=rendered%20server-side&accent=d97706

src/app/api/og/route.tsTypeScript
// /api/og — emits a 1200x630 SVG you can use as <meta property="og:image">
export async function GET(req: Request) {
  const url = new URL(req.url);
  const title = url.searchParams.get("title") ?? "bext demos";
  const svg = '<svg ...><text>' + escapeXml(title) + '</text></svg>';
  return new Response(svg, {
    status: 200,
    headers: {
      "content-type": "image/svg+xml; charset=utf-8",
      "cache-control": "public, max-age=3600",  // query-string keys the cache
    },
  });
}

// Per-page metadata referencing the dynamic image.
export function generateMetadata({ searchParams }) {
  const title = searchParams.title ?? "bext";
  return {
    openGraph: {
      images: ["/api/og?title=" + encodeURIComponent(title)],
    },
  };
}