Tutorials10 min read

OG Image Best Practices for Developers (Next.js, Vercel, Astro)

Developer-focused guide to implementing OG images in Next.js, Vercel, Astro, and vanilla HTML. Includes code examples, caching strategies, and common mistakes.

Pixola Team·

OG images are one of those things that developers know matter but rarely get right. The meta tags feel simple enough, but platform-specific quirks, caching behavior, and framework conventions turn it into a surprisingly tricky problem.

This guide covers OG image implementation best practices for Next.js, Vercel, Astro, and vanilla HTML — with code examples you can use directly.

Next.js (App Router)

Next.js 13+ with the App Router has first-class support for Open Graph images through the Metadata API.

Static Metadata

Export a metadata object from your page.tsx or layout.tsx:

// app/blog/[slug]/page.tsx
import type { Metadata } from "next"

export const metadata: Metadata = {
  title: "Your Article Title",
  description: "A concise description for social sharing.",
  openGraph: {
    title: "Your Article Title",
    description: "A concise description for social sharing.",
    type: "article",
    publishedTime: "2026-03-24",
    images: [
      {
        url: "https://yoursite.com/og/article-slug.png",
        width: 1200,
        height: 630,
        alt: "Description of the image",
      },
    ],
  },
  twitter: {
    card: "summary_large_image",
    title: "Your Article Title",
    description: "Description for Twitter/X.",
    images: ["https://yoursite.com/og/article-slug.png"],
  },
}

Dynamic Metadata

For pages with dynamic content, use generateMetadata:

// app/blog/[slug]/page.tsx
import type { Metadata } from "next"

type Props = { params: Promise<{ slug: string }> }

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

  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      type: "article",
      publishedTime: post.publishedAt,
      images: [{ url: post.ogImage, width: 1200, height: 630 }],
    },
  }
}

The opengraph-image Convention

Next.js supports a special file convention for OG images. Place a file named opengraph-image.png (or .jpg) in any route folder, and Next.js automatically generates the og:image meta tag:

app/
  blog/
    [slug]/
      page.tsx
      opengraph-image.png    ← auto-detected
  opengraph-image.png        ← fallback for all pages

For dynamic OG images, create opengraph-image.tsx and export a function that generates the image using @vercel/og or next/og:

// 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 OGImage({
  params,
}: {
  params: { slug: string }
}) {
  const post = await getPost(params.slug)

  return new ImageResponse(
    (
      <div style={{
        display: "flex",
        flexDirection: "column",
        justifyContent: "center",
        padding: 60,
        width: "100%",
        height: "100%",
        background: "linear-gradient(135deg, #0f0f0f, #1a1a2e)",
        color: "#fff",
      }}>
        <h1 style={{ fontSize: 48, lineHeight: 1.2 }}>{post.title}</h1>
        <p style={{ fontSize: 24, color: "#888" }}>{post.excerpt}</p>
      </div>
    ),
    { ...size }
  )
}

Vercel OG (@vercel/og)

Vercel's @vercel/og library generates images at the edge using Satori (JSX-to-SVG) and Resvg (SVG-to-PNG). It is the standard approach for dynamic OG images on Vercel.

Key Constraints

  • Uses a subset of CSS (flexbox only, no grid, no position: absolute in some contexts)
  • Custom fonts must be loaded explicitly (no system fonts)
  • The tw prop enables Tailwind-like utility classes
  • Images must be passed as ArrayBuffers or base64, not URLs (in Edge Runtime)

Performance Tips

  • Cache the font loading outside the handler so it is not re-fetched on every request
  • Keep the JSX simple — complex layouts increase render time
  • Use Cache-Control headers to cache generated images at the CDN edge

Astro

Astro does not have a built-in OG image convention, but you can set meta tags in your layout's <head>:

---
// src/layouts/BlogPost.astro
const { title, description, ogImage } = Astro.props
const canonicalUrl = new URL(Astro.url.pathname, Astro.site)
---

<html>
  <head>
    <meta property="og:title" content={title} />
    <meta property="og:description" content={description} />
    <meta property="og:image" content={ogImage} />
    <meta property="og:url" content={canonicalUrl} />
    <meta property="og:type" content="article" />
    <meta name="twitter:card" content="summary_large_image" />
    <meta name="twitter:image" content={ogImage} />
  </head>
  <body>
    <slot />
  </body>
</html>

For dynamic OG image generation in Astro, you can create an API route that uses @vercel/og or satori directly if deploying to Vercel, or use a build-time script for static sites.

Vanilla HTML / Static Sites

For static sites, add the meta tags directly in your <head>. See the Open Graph Meta Tags Cheat Sheet for a complete copy-paste template.

The most common mistake with static sites is using relative image paths. OG images must be absolute URLs:

<!-- Wrong -->
<meta property="og:image" content="/images/og.png" />

<!-- Correct -->
<meta property="og:image" content="https://yoursite.com/images/og.png" />

Image Optimization Best Practices

Dimensions

  • Standard:1200 × 630 pixels (1.91:1 ratio)
  • Minimum:600 × 315 pixels (platforms may not render large cards below this)
  • Maximum:Keep under 4096 × 4096 for Twitter/X

For platform-specific dimensions, see the OG Image Size Guide.

File Format and Size

  • PNG for images with text, logos, or sharp edges
  • JPEG for photographs or complex images (smaller file size)
  • Under 1 MB is ideal; platforms may time out on larger files during scraping
  • Under 5 MB is the hard limit for Twitter/X

Safe Zones

Keep important content (text, logos) away from the edges. Different platforms crop differently, and some add rounded corners or overlays. A safe zone of at least 60 pixels from each edge ensures nothing gets cut off.

Platform Caching and Cache Busting

Social platforms cache OG data aggressively. When you update your OG image, the old one may persist for hours or days. Each platform has a tool to force a refresh:

  • Facebook:Sharing Debugger — paste your URL and click “Scrape Again”
  • Twitter/X: Card Validator
  • LinkedIn: Post Inspector

For programmatic cache busting, append a version query parameter to your image URL: og-image.png?v=2. Platforms treat this as a new URL and re-fetch the image.

See How to Test Your Open Graph Images for a detailed walkthrough of each platform's debugging tool.

Common Developer Mistakes

  1. Not specifying og:image dimensions. Without og:image:width and og:image:height, platforms must download the image first. This adds latency and can cause layout shifts in preview cards.
  2. Using localhost or staging URLs. OG images are fetched by platform servers, not browsers. localhost, 127.0.0.1, and private staging URLs will not resolve.
  3. Missing canonical URL. Without og:url, platforms may split share counts across different URL variants of the same page.
  4. Not testing in production. OG images work differently in development vs production because platform scrapers cannot access your dev server. Always test with the production URL.
  5. Forgetting twitter:card. Without this tag, Twitter/X falls back to the small summary card instead of the large image card, even if og:image is set.

Skip the Boilerplate — Generate with AI

If you are spending more time designing OG image templates than writing code, try generating them with AI instead. Pixola's OG Image Generatorcreates unique 1200 × 630 images from text input — no design tools needed.

Generate OG Images with AI

Stop using the same templates as everyone else. Create unique Open Graph images with AI on Pixola.

Try OG Image Generator — Free
og imagenext.jsvercelastrodevelopers