← Imago API

How to Add Open Graph Images to Your Next.js App with One API Call

Published March 5, 2026 · 12 min read · Tutorial

You've built your Next.js app. It's fast, it looks great. Then you share a link on Twitter and... a grey box appears where the preview image should be. Every developer has been here.

Open Graph images are the difference between a link that gets ignored and one that gets clicked. This tutorial will walk you through generating dynamic, per-page OG images for your Next.js app using Imago API — with real, copy-paste code for both the App Router and the Pages Router.

By the end of this tutorial, every page in your app will have a unique, branded OG image generated from the page's title and description. No Figma, no templates, no design work.

What We're Building

We'll build a helper that takes any page's title and description, calls the Imago API, and returns the URL of a generated OG image. Then we'll wire it up to Next.js metadata so every page gets its own preview image automatically.

The result: when someone shares any page from your app, they'll see a clean, branded card with the page title and description — generated on-the-fly, no manual image creation required.

Prerequisites

1

Get Your API Key

Sign up at imagoapi.com/pricing and grab your API key. The free tier gives you 100 images per month — more than enough for testing and small projects. Add it to your .env.local:

.env.local
IMAGO_API_KEY=imago_your_api_key_here
2

Create a Helper Function

Create a utility file that wraps the Imago API. We'll use this from both route handlers and server components:

lib/og-image.ts
// lib/og-image.ts
export interface OGImageOptions {
  title: string;
  description?: string;
  brandColor?: string;
  template?: 'gradient' | 'solid' | 'minimal';
}

export async function generateOGImage(options: OGImageOptions): Promise<string | null> {
  const apiKey = process.env.IMAGO_API_KEY;
  if (!apiKey) {
    console.warn('IMAGO_API_KEY not set — skipping OG image generation');
    return null;
  }

  try {
    const res = await fetch('https://imagoapi.com/api/og/generate', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        api_key: apiKey,
        title: options.title,
        description: options.description ?? '',
        brand_color: options.brandColor ?? '#6366F1',
        template: options.template ?? 'gradient',
      }),
      // Cache for 24h — don't regenerate the same image every request
      next: { revalidate: 86400 },
    });

    if (!res.ok) return null;
    const data = await res.json();
    return data.image?.url ?? null;
  } catch {
    return null;
  }
}
💡 Tip: We're using Next.js's next: { revalidate: 86400 } fetch option so the same title+description combo only generates a new image once per day. This keeps your API usage low and your pages fast.
3

Wire It Up — App Router

In Next.js 13+ with the App Router, you export a generateMetadata function from your page. This runs on the server and can call async APIs:

app/blog/[slug]/page.tsx
import { generateOGImage } from '@/lib/og-image';
import type { Metadata } from 'next';

interface Props {
  params: { slug: string };
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  // Fetch your post data (replace with your actual data source)
  const post = await getPost(params.slug);

  const ogImageUrl = await generateOGImage({
    title: post.title,
    description: post.excerpt,
    brandColor: '#6366F1',
  });

  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      images: ogImageUrl ? [{ url: ogImageUrl, width: 1200, height: 630 }] : [],
      type: 'article',
    },
    twitter: {
      card: 'summary_large_image',
      title: post.title,
      description: post.excerpt,
      images: ogImageUrl ? [ogImageUrl] : [],
    },
  };
}

export default async function BlogPost({ params }: Props) {
  const post = await getPost(params.slug);
  return <article>{/* your post content */}</article>;
}
4

Wire It Up — Pages Router

If you're on the Pages Router, generate the OG image URL inside getStaticProps or getServerSideProps and pass it as a prop:

pages/blog/[slug].tsx
import Head from 'next/head';
import { generateOGImage } from '@/lib/og-image';
import type { GetStaticProps } from 'next';

export const getStaticProps: GetStaticProps = async ({ params }) => {
  const post = await getPost(params!.slug as string);
  const ogImageUrl = await generateOGImage({
    title: post.title,
    description: post.excerpt,
  });

  return { props: { post, ogImageUrl }, revalidate: 86400 };
};

export default function BlogPost({ post, ogImageUrl }) {
  return (
    <>
      <Head>
        <title>{post.title}</title>
        <meta name="description" content={post.excerpt} />
        {ogImageUrl && (
          <>
            <meta property="og:image" content={ogImageUrl} />
            <meta property="og:image:width" content="1200" />
            <meta property="og:image:height" content="630" />
            <meta name="twitter:card" content="summary_large_image" />
            <meta name="twitter:image" content={ogImageUrl} />
          </>
        )}
      </Head>
      <article>{/* your post content */}</article>
    </>
  );
}
5

Test It

Run your app and check the generated HTML:

npm run dev
curl http://localhost:3000/blog/my-post | grep og:image

You should see something like:

<meta property="og:image" content="https://imagoapi.com/generated/abc123.png" />

To verify the social card preview, use the Twitter Card Validator or opengraph.xyz.

Performance Considerations

A few things worth knowing before you ship:

Adding a Fallback

For production, add a static fallback image so your OG tags are never empty:

lib/og-image.ts (with fallback)
const FALLBACK_OG_IMAGE = 'https://imagoapi.com/og/default.png'; // your static fallback

export async function getOGImage(options: OGImageOptions): Promise<string> {
  const generated = await generateOGImage(options);
  return generated ?? FALLBACK_OG_IMAGE;
}

That's It

Every page in your Next.js app now gets a unique, branded OG image — generated from the page title and description, no design work required. When you publish a new blog post or add a new product, the image is generated automatically.

The total integration time for this is under 15 minutes. Compare that to maintaining a Figma template, exporting images manually, or uploading custom images for every page.

Ready to Ship Your First OG Image?

Get 100 free images per month. No credit card required.

Get Your Free API Key →