ogmake
Menu

OG images in Next.js

Render OG images outside your app with ogmake: no fonts to bundle, no emoji pipeline, no Flexbox-only limits, and the same URL works from any stack. The pattern is one function that builds a signed URL and one line in generateMetadata.

1. Get a key

Sign in at /login and create a key in the dashboard. The signing secret is shown once. Put both in .env.local, never in client code:

OGMAKE_KEY_ID=k_...
OGMAKE_SIGNING_SECRET=base64-secret-shown-once

Still on a hand-issued beta key? Email support@ogmake.com.

2. Sign a URL on the server

The signature is an HMAC-SHA256 of the sorted, encoded query (the signing page has the exact rules). In Node it is a few lines, so drop this helper into your app:

// lib/ogmake.ts — server-only
import { createHmac } from "node:crypto";

const enc = (v: string) =>
  encodeURIComponent(v).replace(/[!'()*]/g, (c) => "%" + c.charCodeAt(0).toString(16).toUpperCase());

export function ogImageUrl(params: Record<string, string>): string {
  const all: Record<string, string> = { template: "blog", ...params };
  const canonical = Object.keys(all)
    .sort()
    .map((k) => enc(k) + "=" + enc(all[k]))
    .join("&");
  const sig = createHmac("sha256", Buffer.from(process.env.OGMAKE_SIGNING_SECRET!, "base64"))
    .update(canonical)
    .digest("base64url");
  return "https://ogmake.com/i/" + process.env.OGMAKE_KEY_ID + "/" + sig + "?" + canonical;
}

3. Use it in generateMetadata

// app/blog/[slug]/page.tsx
import type { Metadata } from "next";
import { ogImageUrl } from "@/lib/ogmake";

export async function generateMetadata({ params }): Promise<Metadata> {
  const post = await getPost((await params).slug);
  const image = ogImageUrl({ title: post.title, author: post.author, site: "My Blog" });
  return {
    title: post.title,
    openGraph: { images: [{ url: image, width: 1200, height: 630 }] },
    twitter: { card: "summary_large_image", images: [image] },
  };
}

The image renders the first time a crawler requests the URL and is cached afterwards, so nothing is rendered in your build or at request time. Because the URL changes whenever the title changes, a stale preview after an edit is a platform cache problem, not a stale image: see og:image not showing.

Fields per template are on the reference page. If you would rather not sign at all for static pages, a public URL works with just the key id.

What ogmake renders

Moving from an ImageResponse route? See migrating from @vercel/og. Once your tags are live, confirm them with the OG image checker.