Dynamic OGP (Open Graph Protocol) image generation API with @vercel/og. Runs on Edge Runtime for fast, globally distributed image generation.
Built from the OGP system powering 32blog.com (120+ articles with auto-generated social images).
- 3 templates — Default, Blog Post, Social with gradient backgrounds
- Edge Runtime — Fast generation at the edge via
@vercel/og(Satori) - URL-based API — Pass title, subtitle, author via query parameters
- Dark/Light themes — Toggle with
?theme=darkor?theme=light - Preview UI — Built-in preview page to test templates
- 1200x630 — Standard OGP dimensions for Twitter/Facebook/LinkedIn
git clone https://github.com/omitsu-dev/ogp-generator.git
cd ogp-generator
npm install
npm run devOpen http://localhost:3000 for the preview UI.
GET /api/og?title=Hello+World&subtitle=My+subtitle&site=example.com&theme=dark
| Param | Default | Description |
|---|---|---|
title |
"Hello World" | Main heading |
subtitle |
— | Optional subheading |
site |
"My Site" | Site name in header |
theme |
"dark" | dark or light |
GET /api/og/blog?title=My+Post&author=John&date=2026-03-14&category=Tutorial&readTime=5+min
| Param | Default | Description |
|---|---|---|
title |
"Blog Post" | Article title |
author |
— | Author name |
date |
— | Publication date |
category |
— | Category label |
readTime |
— | Reading time |
GET /api/og/social?title=Launch+Day&description=We+are+live&emoji=🚀&gradient=blue
| Param | Default | Description |
|---|---|---|
title |
"My App" | Main heading |
description |
— | Subheading |
emoji |
— | Large emoji above title |
gradient |
"blue" | blue, green, orange, dark |
<meta property="og:image" content="https://your-domain.com/api/og?title=My+Page" />export async function generateMetadata() {
const ogUrl = new URL("/api/og", "https://your-domain.com");
ogUrl.searchParams.set("title", "My Page Title");
return {
openGraph: {
images: [{ url: ogUrl.toString(), width: 1200, height: 630 }],
},
};
}├── app/
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Preview UI
│ └── api/og/
│ ├── route.tsx # Default template
│ ├── blog/route.tsx # Blog post template
│ └── social/route.tsx # Social/gradient template
Create app/api/og/your-template/route.tsx:
import { ImageResponse } from "@vercel/og";
import { NextRequest } from "next/server";
export const runtime = "edge";
export async function GET(req: NextRequest) {
const title = req.nextUrl.searchParams.get("title") ?? "Default";
return new ImageResponse(
(
<div style={{ width: "100%", height: "100%", display: "flex",
alignItems: "center", justifyContent: "center",
backgroundColor: "#000", color: "#fff", fontSize: "64px" }}>
{title}
</div>
),
{ width: 1200, height: 630 }
);
}| Package | Version |
|---|---|
| Next.js | 16.x |
| @vercel/og | 0.6.x |
| React | 19.x |
| Tailwind CSS | 4.x |