OG Images in Next.js Pages Router
Generate OG images in Next.js Pages Router with FastOG's credit-based API — sign up free for 100 credits (create your API key in the dashboard). Use the Free OG Image Tester to preview any template, then wire up dynamic Open Graph images with HMAC-signed URLs in your Pages Router routes.
Setup
1. Create the signer utility
javascript
// lib/og-image-signer.js
import crypto from "crypto";
export function generateSignedOgUrl(params, options = {}) {
const {
baseUrl = "https://fastog.com/api/v1/og",
apiKey = process.env.FASTOG_API_KEY,
hmacSecret = process.env.FASTOG_HMAC_SECRET,
} = options;
if (!apiKey || !hmacSecret) {
throw new Error("Missing FASTOG_API_KEY or FASTOG_HMAC_SECRET");
}
const timestamp = Math.floor(Date.now() / 1000);
const url = new URL(baseUrl);
for (const [key, value] of Object.entries(params)) {
url.searchParams.set(key, value);
}
const apiKey = apiKey;
const path = url.pathname;
const message = `GET${path}${timestamp}${apiKey}`;
const signature = crypto
.createHmac("sha256", hmacSecret)
.update(message)
.digest("hex");
url.searchParams.set("key", apiKey);
url.searchParams.set("s", signature);
url.searchParams.set("t", timestamp);
return url.toString();
}2. Environment variables
bash
# .env.local
FASTOG_API_KEY=sk_your_full_api_key_here
FASTOG_HMAC_SECRET=your_64_char_hex_secret_hereStatic Site Generation (SSG)
typescript
// pages/blog/[slug].tsx
import Head from "next/head";
import { generateSignedOgUrl } from "@/lib/og-image-signer";
export default function BlogPost({ post, ogImageUrl }) {
return (
<>
<Head>
<meta property="og:image" content={ogImageUrl} />
<meta property="og:title" content={post.title} />
<meta property="og:description" content={post.excerpt} />
</Head>
<article>
<h1>{post.title}</h1>
<p>{post.excerpt}</p>
</article>
</>
);
}
export async function getStaticPaths() {
const posts = await getAllPosts();
return {
paths: posts.map((post) => ({ params: { slug: post.slug } })),
fallback: "blocking",
};
}
export async function getStaticProps({ params }) {
const post = await getPost(params.slug);
const ogImageUrl = generateSignedOgUrl({
template: "blog",
title: post.title,
subtitle: post.excerpt,
});
return {
props: {
post,
ogImageUrl,
},
revalidate: 3600,
};
}Server-Side Rendering (SSR)
typescript
// pages/products/[id].tsx
import Head from "next/head";
import { generateSignedOgUrl } from "@/lib/og-image-signer";
export default function Product({ product, ogImageUrl }) {
return (
<>
<Head>
<meta property="og:image" content={ogImageUrl} />
<meta property="og:title" content={product.name} />
</Head>
<main>
<h1>{product.name}</h1>
<p>{product.price}</p>
</main>
</>
);
}
export async function getServerSideProps({ params }) {
const product = await getProduct(params.id);
const ogImageUrl = generateSignedOgUrl({
template: "ecommerce",
title: product.name,
price: product.price,
image: product.image,
});
return {
props: {
product,
ogImageUrl,
},
};
}API Route Proxy
typescript
// pages/api/og.ts
import type { NextApiRequest, NextApiResponse } from "next";
import { generateSignedOgUrl } from "@/lib/og-image-signer";
export default function handler(req: NextApiRequest, res: NextApiResponse) {
const { title, template = "default" } = req.query;
if (!title) {
return res.status(400).json({ error: "Missing title" });
}
const ogImageUrl = generateSignedOgUrl({
template: template as string,
title: title as string,
});
res.json({ ogImageUrl });
}Common Pitfalls
Timestamp in Milliseconds
❌ Wrong: Date.now() → 1722718800000
✅ Correct: Math.floor(Date.now() / 1000) → 1722718800
Wrong Message Format
❌ Wrong: GET:/api/v1/og:1722718800:sk_abc12
✅ Correct: GET/api/v1/og1722718800sk_abc12
Full API Reference
Related
Try it free — no signup required
Preview and test dynamic OG images in seconds.
Open the Free OG Image Tester