DEV Community

Corbanware
Corbanware

Posted on

How to Add OG Images to Next.js in 5 Minutes

How to Add OG Images to Next.js in 5 Minutes

If you have ever shared a link on Twitter or Slack and watched it render as a sad, plain-text URL with no preview image, you already understand why OG images matter. Those 1200x630 pixel cards are the difference between a link that gets clicked and one that gets scrolled past. Studies show that links with rich preview images get 2-3x more engagement than bare text links.

The good news: if you are building with Next.js and the App Router, adding OG images is remarkably straightforward. Let me walk you through three approaches, from the simplest to the most powerful.

The Foundation: Understanding OG Meta Tags

Before we touch any Next.js code, here is what the browser needs to see in your HTML <head>:

<meta property="og:image" content="https://yoursite.com/og.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:title" content="Your Page Title" />
<meta property="og:description" content="A brief description" />
Enter fullscreen mode Exit fullscreen mode

That is it. Every social platform (Facebook, Twitter/X, LinkedIn, Slack, Discord, WhatsApp) reads these tags to build the preview card. The image URL must be absolute -- relative paths will not work.

Approach 1: Static Metadata (30 Seconds)

For pages where the OG image never changes (your homepage, about page, pricing page), use the metadata export in your layout or page file:

// app/layout.tsx
import type { Metadata } from "next";

export const metadata: Metadata = {
  title: "My Awesome App",
  description: "The best thing since sliced bread",
  openGraph: {
    title: "My Awesome App",
    description: "The best thing since sliced bread",
    images: [
      {
        url: "https://yoursite.com/og-homepage.png",
        width: 1200,
        height: 630,
        alt: "My Awesome App - homepage preview",
      },
    ],
  },
  twitter: {
    card: "summary_large_image",
  },
};
Enter fullscreen mode Exit fullscreen mode

Next.js automatically renders the correct <meta> tags in the HTML head. No manual string concatenation, no dangerouslySetInnerHTML. It even handles the Twitter card fallback for you.

Pro tip: Always include the twitter.card: "summary_large_image" property. Without it, Twitter defaults to the small square card, which wastes most of your carefully designed image.

Approach 2: Dynamic generateMetadata (2 Minutes)

This is where things get interesting. For blogs, documentation, or any page with dynamic content, you want a unique OG image per page. The generateMetadata async function lets you compute metadata at request time:

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

interface Props {
  params: Promise<{ slug: string }>;
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { slug } = await params;
  const post = await getPostBySlug(slug);

  if (!post) return {};

  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      type: "article",
      publishedTime: post.publishedAt,
      images: [
        {
          url: `https://yoursite.com/api/og?title=${encodeURIComponent(post.title)}`,
          width: 1200,
          height: 630,
        },
      ],
    },
    twitter: { card: "summary_large_image" },
  };
}
Enter fullscreen mode Exit fullscreen mode

Notice the OG image URL points to an API route. That brings us to the next question: how do you actually generate the image?

Approach 3: Generating Images on the Fly

You have two main options here.

Option A: @vercel/og (Self-Hosted)

Vercel's @vercel/og library lets you write JSX that renders as an image. It uses Satori under the hood -- a layout engine that converts React components to SVG, then rasterizes to PNG.

// app/api/og/route.tsx
import { ImageResponse } from "next/og";

export const runtime = "edge";

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const title = searchParams.get("title") ?? "My Blog";

  return new ImageResponse(
    <div style={{ width: '100%', height: '100%', display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', color: 'white', padding: 60 }}>
      <h1 style={{ fontSize: 64, fontWeight: 700, textAlign: 'center' }}>{title}</h1>
    </div>,
    { width: 1200, height: 630 }
  );
}
Enter fullscreen mode Exit fullscreen mode

This works well if you want full control over the design and are comfortable with Satori's CSS limitations.

Option B: External API

If you would rather not maintain your own image generation endpoint, an external API simplifies things significantly. For example, with ogimg.xyz, your dynamic metadata becomes:

const ogUrl = new URL("https://ogimg.xyz/api/og"?utm_source=devto&utm_medium=article&utm_campaign=wf5);
ogUrl.searchParams.set("title", post.title);
ogUrl.searchParams.set("description", post.excerpt);
ogUrl.searchParams.set("template", "gradient");
Enter fullscreen mode Exit fullscreen mode

No API route to maintain, no font files to bundle, no Satori quirks to debug. ogimg.xyz offers a free tier of 50 images/month, which is enough for most personal blogs and small projects.

Testing Your OG Images

After deploying, always verify your images actually work:

  1. Facebook Sharing Debugger -- Paste your URL and hit Scrape Again to force a fresh fetch.
  2. Twitter Card Validator -- Preview exactly what Twitter will render.
  3. LinkedIn Post Inspector -- Check how LinkedIn will render your shared link.
  4. Open your browser DevTools -- Search the HTML head for og:image and make sure the URL loads.

Quick Checklist

  • og:image URL is absolute (starts with https://)
  • Image dimensions are 1200x630
  • File size is under 1MB (5MB max for most platforms)
  • twitter:card is set to summary_large_image
  • Image renders correctly when you open the URL directly
  • You have tested with at least one social platform debugger

Wrapping Up

Adding proper OG images to a Next.js app is one of those 5-minute tasks that pays dividends every time someone shares your content. Whether you go with static metadata, dynamic generateMetadata, self-hosted @vercel/og, or an external service, the important thing is that you do it.

Your links deserve better than plain text.


Have questions about OG images in Next.js? Drop a comment below -- I read every one.

Top comments (0)