You launched your Next.js blog. It looks great in the browser.
Then someone shares a post on Twitter and it shows up as a blank card with no image, no title, just a raw URL. You get 4 clicks instead of 400.
Here's how to fix it in under 10 minutes — and make every post look professional when shared anywhere.
What Next.js Gives You Out of the Box
Next.js has excellent metadata support via the generateMetadata function (App Router) or Head component (Pages Router). But it only generates the tags — you still need to provide an actual image file for og:image.
Most tutorials skip this part. They show you how to add the tags but leave you to figure out where the image comes from.
The App Router Approach
In your app/blog/[slug]/page.tsx:
import type { Metadata } from 'next'
export async function generateMetadata({ params }): Promise<Metadata> {
const post = await getPost(params.slug)
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [
{
url: `https://yourdomain.com/og/${params.slug}.png`,
width: 1200,
height: 630,
alt: post.title,
}
],
},
twitter: {
card: 'summary_large_image',
title: post.title,
description: post.excerpt,
images: [`https://yourdomain.com/og/${params.slug}.png`],
},
}
}
Pages Router Approach
In your blog post component:
import Head from 'next/head'
export default function BlogPost({ post }) {
return (
<>
<Head>
<meta property="og:title" content={post.title} />
<meta property="og:description" content={post.excerpt} />
<meta property="og:image" content={`https://yourdomain.com/og/${post.slug}.png`} />
<meta property="og:type" content="article" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content={`https://yourdomain.com/og/${post.slug}.png`} />
</Head>
{/* post content */}
</>
)
}
The Part Everyone Skips: Creating the Images
Both approaches assume you have a 1200×630 PNG at /public/og/[slug].png.
You have three options:
Option 1: Dynamic generation with @vercel/og
Generates images on the fly via Edge Runtime. Powerful but requires setup, and adds latency to every share.
Option 2: Manual Figma/Canva for each post
Works but doesn't scale. You'll stop doing it by post #5.
Option 3: Generate once per post with a browser tool
This is what most indie bloggers actually do. Open OGForge, type your post title, pick a template, download the PNG, drop it in /public/og/. Takes 30 seconds per post.
For personal blogs and small teams, Option 3 is the right call. No infrastructure, no build step, no maintenance.
Testing Your Tags
After deployment, test with:
- opengraph.xyz — shows exactly what Twitter/LinkedIn will render
- LinkedIn Post Inspector:
linkedin.com/post-inspector - Twitter Card Validator:
cards-dev.twitter.com/validator
One thing to watch: platforms cache OG data aggressively. If you update an image, use the validator tools to force a cache refresh before sharing.
The Checklist
Before publishing any Next.js blog post:
- [ ]
og:titleset - [ ]
og:descriptionset (under 200 chars) - [ ]
og:imageset and pointing to a real 1200×630 PNG - [ ]
twitter:cardset tosummary_large_image - [ ] Image tested in opengraph.xyz
Your posts are already good. Stop letting a missing PNG be the reason nobody clicks them.
Top comments (0)