OG Images

Dynamic Open Graph images using next/og ImageResponse. No dependencies — built into Next.js.

Generate branded 1200x630 social cards for every page using next/og's ImageResponse. Renders JSX at the edge — no Figma, no static files.

Setup

1

Create the OG image route

Create app/api/og/route.tsx:

import { ImageResponse } from "next/og"
import type { NextRequest } from "next/server"

export const runtime = "edge"

const SITE_NAME = "YourSaaS"
const SITE_URL = process.env.NEXT_PUBLIC_APP_URL ?? "https://yoursaas.com"

async function loadFont(): Promise<ArrayBuffer> {
  const res = await fetch(
    "https://fonts.gstatic.com/s/plusjakartasans/v8/LDIbaomQNQcsA88c7O9yZ4KMCoOg4IA6-91aHEjcWuA_KU7NShXUEKi4Rw.woff2"
  )
  return res.arrayBuffer()
}

export async function GET(req: NextRequest) {
  const { searchParams } = new URL(req.url)
  const title = searchParams.get("title") ?? SITE_NAME
  const description = searchParams.get("description") ?? "Ship your SaaS faster"
  const fontData = await loadFont()

  return new ImageResponse(
    (
      <div style={{ height: "100%", width: "100%", display: "flex", flexDirection: "column", justifyContent: "center", padding: "80px", background: "linear-gradient(135deg, #0f0f0f 0%, #1a1a2e 100%)", color: "#ffffff" }}>
        <div style={{ fontSize: "20px", color: "rgba(255,255,255,0.6)", marginBottom: "32px", letterSpacing: "2px", textTransform: "uppercase" }}>{SITE_NAME}</div>
        <div style={{ fontSize: "64px", fontWeight: 800, lineHeight: 1.15, marginBottom: "24px", color: "#ffffff" }}>{title}</div>
        <div style={{ fontSize: "28px", color: "rgba(255,255,255,0.6)", lineHeight: 1.5 }}>{description}</div>
        <div style={{ position: "absolute", bottom: "40px", right: "80px", fontSize: "18px", color: "rgba(255,255,255,0.3)" }}>{SITE_URL.replace(/https?:\/\//, "")}</div>
      </div>
    ),
    {
      width: 1200,
      height: 630,
      fonts: [{ name: "Plus Jakarta Sans", data: fontData, style: "normal", weight: 800 }],
    }
  )
}
2

Add to page metadata

// Any page.tsx or layout.tsx
import type { Metadata } from "next"

const APP_URL = process.env.NEXT_PUBLIC_APP_URL ?? "https://yoursaas.com"

export const metadata: Metadata = {
  openGraph: {
    images: [{
      url: `${APP_URL}/api/og?title=${encodeURIComponent("My Page")}&description=${encodeURIComponent("Page description")}`,
      width: 1200,
      height: 630,
    }],
  },
  twitter: {
    card: "summary_large_image",
    images: [`${APP_URL}/api/og?title=${encodeURIComponent("My Page")}`],
  },
}
3

Optional: create a helper

// lib/og.ts
const APP_URL = process.env.NEXT_PUBLIC_APP_URL ?? "https://yoursaas.com"

export function ogImageUrl(title: string, description?: string): string {
  const params = new URLSearchParams({ title })
  if (description) params.set("description", description)
  return `${APP_URL}/api/og?${params.toString()}`
}

Environment Variables

NEXT_PUBLIC_APP_URL="https://yourdomain.com"

Verification

  1. Start dev: pnpm dev
  2. Open /api/og?title=Hello%20World&description=Testing
  3. A 1200x630 PNG should render in the browser
  4. Test in Facebook Sharing Debugger or Twitter Card Validator

Notes

  • ImageResponse runs at the edge — no Node.js required
  • Results are cached at the CDN edge by default
  • No npm dependencies — next/og is built into Next.js

Demo Mode — Explore freely. Some actions are restricted. demo@launchfst.dev / demo1234

Get LaunchFst →