Generating Dynamic OG Images for Your Next.js Blog

July 24, 2024

23 min read

An Open Graph image or OG image is the image that is displayed on social media accounts when you or someone else post a link to an article or a video from your website. By default, Open Graph meta tags determine how URLs are displayed when shared on social media sites.

Setting Up the API Route

The cornerstone of our dynamic OG image generation is the og/route.js file:

import { ImageResponse } from "next/og";

export function GET(request: Request) {
  let url = new URL(request.url);
  let title = url.searchParams.get("title") || "Next.js Blog";
  
  try {
    return new ImageResponse(
      // JSX structure here
    );
  } catch (error) {
    return new Response("Failed to create OG Image", { status: 500 });
  }
}

This API route uses Next.js's ImageResponse to generate our OG image on-the-fly. We extract the title from the URL parameters, defaulting to "Next.js Blog" if no title is provided.

Designing the OG Image

The heart of our OG image generation lies in the JSX structure we pass to ImageResponse:

<div
  style={{
    display: "flex",
    height: "100%",
    width: "100%",
    alignItems: "center",
    justifyContent: "center",
    letterSpacing: "-.02em",
    fontWeight: 700,
    background: "white",
  }}
>
  {/* Logo and website URL */}
  <div
    style={{
      left: 42,
      top: 42,
      position: "absolute",
      display: "flex",
      alignItems: "center",
    }}
  >
    <span
      style={{
        width: 24,
        height: 24,
        background: "black",
        color: "white",
        padding: "2px 25px 2px 2px",
      }}
    >
      BLOG
    </span>
    <span
      style={{
        marginLeft: 8,
        fontSize: 20,
      }}
    >
      // your website URL
    </span>
  </div>
  
  {/* Blog post title */}
  <div
    style={{
      display: "flex",
      flexWrap: "wrap",
      justifyContent: "center",
      padding: "20px 50px",
      margin: "0 42px",
      fontSize: 40,
      width: "auto",
      maxWidth: 550,
      textAlign: "center",
      backgroundColor: "black",
      color: "white",
      lineHeight: 1.4,
    }}
  >
    {title}
  </div>
</div>

This structure creates a visually appealing OG image with:

Integrating with Blog Posts

To integrate this dynamic OG image generation into our blog post pages, we use the generateMetadata function in blog/[slug]/page.jsx:

export function generateMetadata({
  params,
}: {
  params: { slug: string };
}) {
  let post = getBlogPosts().find((post) => post.slug === params.slug);
  if (!post) {
    return;
  }
  let {
    title,
    publishedAt: publishedTime,
    summary: description,
    image,
  } = post.metadata;
  let ogImage = image
    ? image
    : `${baseUrl}/og?title=${encodeURIComponent(title)}`;
  
  return {
    title,
    description,
    openGraph: {
      title,
      description,
      type: "article",
      publishedTime,
      url: `${baseUrl}/blog/${post?.slug}}`,
      images: [{ url: ogImage }],
    },
    twitter: {
      card: "summary_large_image",
      title,
      description,
      images: [ogImage],
    },
  };
}

This function creates metadata for each blog post, including OpenGraph and Twitter card metadata for optimal sharing.

SEO Optimization

To enhance SEO, we include structured data using JSON-LD:

<script
  type="application/ld+json"
  suppressHydrationWarning
  dangerouslySetInnerHTML={{
    __html: JSON.stringify({
      "@context": "https://schema.org",
      "@type": "BlogPosting",
      headline: post?.metadata.title,
      datePublished: post?.metadata?.publishedAt,
      dateModified: post.metadata.publishedAt,
      description: post.metadata.summary,
      image: post.metadata.image
        ? `${baseUrl}${post.metadata.image}`
        : `/og?title=${encodeURIComponent(post.metadata.title)}`,
      url: `${baseUrl}/blog/${post.slug}`,
      author: {
        "@type": "Person",
        name: "Next.js Blog",
      },
    }),
  }}
/>

This structured data helps search engines better understand and index our content.

Performance Considerations

To optimize performance, we use generateStaticParams:

export async function generateStaticParams() {
  let posts = getBlogPosts();
  return posts.map((post) => ({
    slug: post.slug,
  }));
}

This function pre-renders our blog post pages at build time, improving load times and SEO.