Keynou Logo

Blog

Tips, tutorials, and insights about online tools

How to Add OG Images to Next.js App Router (With Dynamic Generation)
2026-06-23Keynou Team

How to Add OG Images to Next.js App Router (With Dynamic Generation)

Next.js App Router gives you three ways to handle OG images. Most developers pick the wrong one for their use case. This guide explains all three, when to use each, and how to set up fully dynamic, auto-generated OG images for every page on your site.

Method 1: Static opengraph-image.png File

The simplest method: place an image file in any route folder and Next.js automatically serves it as the OG image for that route.

app/
  blog/
    opengraph-image.png   ← used for /blog
  tools/
    opengraph-image.png   ← used for /tools

When to use it: Static pages that rarely change (home page, about page). You design it once in Figma, export it, and forget it.

Limitations: Every route needs its own file. You can't personalise the image with dynamic content like a post title.

Method 2: opengraph-image.tsx — Server-Side JSX Images

Next.js supports a special opengraph-image.tsx (or .jsx) file in any route folder. It uses ImageResponse from next/og to render JSX to a PNG server-side.

// app/blog/[slug]/opengraph-image.tsx
import { ImageResponse } from 'next/og';

export const size = { width: 1200, height: 630 };
export const contentType = 'image/png';

export default async function Image({ params }) {
  const post = await getPostBySlug(params.slug);

  return new ImageResponse(
    <div style={{ background: '#1e1b4b', color: 'white', width: '100%', height: '100%', display: 'flex', alignItems: 'center', padding: 80 }}>
      <h1 style={{ fontSize: 64, fontWeight: 800 }}>{post.title}</h1>
    </div>,
    { ...size }
  );
}

Next.js automatically wires up the og:image meta tag — you don't need to set anything in generateMetadata.

When to use it: Per-route dynamic images where you want Next.js to handle the plumbing automatically.

Limitations: The file lives inside the route folder, so you need one per dynamic route type. Also runs at request time (or build time with static generation), which can add latency.

Method 3: Custom /api/og Route — Most Flexible

Build a standalone Edge Function that generates OG images from URL query parameters. This is the most powerful approach because one route generates images for your entire site.

// app/api/og/route.jsx
import { ImageResponse } from '@vercel/og';

export const runtime = 'edge';

export async function GET(req) {
  const { searchParams } = new URL(req.url);
  const title = searchParams.get('title') || 'My Site';
  const description = searchParams.get('description') || '';
  const category = searchParams.get('category') || '';

  return new ImageResponse(
    <div style={{ /* your design */ }}>
      <h1>{title}</h1>
      <p>{description}</p>
    </div>,
    { width: 1200, height: 630 }
  );
}

Then in your generateMetadata:

export async function generateMetadata({ params }) {
  const post = await getPostBySlug(params.slug);

  const ogImage = `https://yoursite.com/api/og?title=${encodeURIComponent(post.title)}&category=blog`;

  return {
    openGraph: {
      images: [{ url: ogImage, width: 1200, height: 630 }],
    },
    twitter: {
      card: 'summary_large_image',
      images: [ogImage],
    },
  };
}

Every blog post, tool page, and static page now gets a unique, beautifully branded image from a single template.

When to use it: Large sites with many dynamic routes — blogs, tool directories, documentation sites, e-commerce.

Setting Up @vercel/og

Install the package:

npm install @vercel/og

The key constraint is that Edge Functions can't use Node.js APIs — no fs, no path, no canvas. You can only use:

  • Inline CSS-in-JS styles (as plain objects, not Tailwind classes)
  • Web fonts loaded from a URL
  • Base64-encoded images embedded directly

For fonts, load them at the top of your route:

const fontData = await fetch('https://yoursite.com/fonts/Inter-Bold.ttf').then(r => r.arrayBuffer());

return new ImageResponse(jsx, {
  width: 1200,
  height: 630,
  fonts: [{ name: 'Inter', data: fontData, weight: 700 }],
});

Using the Keynou OG Generator Without Coding

If you don't want to build your own /api/og route, Keynou's OG Image Generator gives you the same output instantly:

  1. Enter your title and description
  2. Choose a category (controls the accent colour)
  3. Set your domain, logo letter, and colours once — they save automatically
  4. Copy the URL and paste it into your meta tags

The tool generates the same Edge-rendered 1200×630 PNG that you'd build yourself — with a polished dark gradient template that matches any professional website.

Verifying Your OG Image Works

After deploying, test with:

  • Twitter Card Validator: cards-dev.twitter.com/validator
  • Facebook Sharing Debugger: developers.facebook.com/tools/debug
  • LinkedIn Post Inspector: linkedin.com/post-inspector
  • OpenGraph.xyz: opengraph.xyz — shows what all platforms see

Common Issues

Image not updating after deployment? Social platforms aggressively cache OG images. Use the platform debugger tools to force a re-scrape, or add a cache-busting query string (?v=2).

Image shows as blank on Twitter? Make sure your og:image URL is absolute (https://...), not relative. Twitter rejects relative URLs.

CORS errors? Your /api/og route needs to return proper headers. ImageResponse from @vercel/og handles this automatically.

Fonts not rendering? Edge runtime blocks synchronous font loading. Use await fetch() at the top of your handler, not inside the JSX template.

Related Resources

FlowDrive