What your links look like when you're not looking
You spend weeks building a beautiful landing page. Then someone shares it on Slack, and the preview shows... nothing. No image, no title, just a bare URL. That's what happens when you skip Open Graph tags.
Open Graph (OG) tags are meta tags that control how your page appears when shared on social media, messaging apps, and anywhere else that generates link previews. Without them, platforms guess what to show - and they usually guess wrong. An open graph checker renders the preview exactly as Slack, LinkedIn, and Facebook will build it, so you find out before your audience does.
The four essential OG tags
At minimum, every page should have these four tags:
<meta
property="og:title"
content="LintPage - Pre-Launch SEO Linting"
/>
<meta
property="og:description"
content="Catch SEO disasters before launch."
/>
<meta
property="og:image"
content="https://lintpage.com/og-image.png"
/>
<meta
property="og:url"
content="https://lintpage.com/"
/>
- og:title - The headline in the link preview. Can differ from your
<title>tag (and often should - social titles can be more conversational). - og:description - The supporting text below the title. Keep it under 200 characters.
- og:image - The preview image. This is the single biggest factor in whether someone clicks a shared link.
- og:url - The canonical URL for the content. Helps platforms deduplicate shares.
Getting the image right
The og:image is the most impactful tag and the most commonly broken. Here's what you need to know:
Size: 1200x630 pixels (1.91:1 aspect ratio). This works across Facebook, Twitter, LinkedIn, and Slack. Some platforms crop slightly, so keep important content centered.
Format: Use PNG or JPG. Keep the file size under 5MB. Some platforms won't load images over 8MB.
URL: Must be an absolute URL starting with https://. Relative paths don't work. The image must be publicly accessible - platforms fetch it from their servers, not from the user's browser.
<!-- Won't work: relative path -->
<meta
property="og:image"
content="/images/og.png"
/>
<!-- Will work: absolute URL -->
<meta
property="og:image"
content="https://example.com/images/og.png"
/>
Generating OG images dynamically
Hand-making an image per post does not scale. Next.js can render one per route at request time using JSX, which means the image stays in sync with the content automatically. Create opengraph-image.tsx beside the page:
import { ImageResponse } from 'next/og';
export const size = { width: 1200, height: 630 };
export const contentType = 'image/png';
export default async function Image({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const post = await getPost(slug);
return new ImageResponse(
(
<div
style={{
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
padding: 80,
background: '#0a0a0a',
color: '#e7ecf3',
fontSize: 64,
}}
>
<div style={{ fontSize: 24, opacity: 0.6 }}>example.com</div>
<div>{post.title}</div>
</div>
),
{ ...size },
);
}
Next.js generates the file, serves it, and injects the matching og:image tags with the correct absolute URL and dimensions. Two things to know: the route name carries a build-specific hash, so old URLs 404 after a deploy (harmless - crawlers refetch from the tag), and only a subset of CSS is supported, so keep the layout to flexbox, solid colours, and text.
What each platform actually shows
"Open Graph" is a shared vocabulary, not a shared renderer. The same four tags produce noticeably different cards depending on where the link lands, which is why a preview that looks right in one place can look broken in another.
- Slack shows a compact card with a coloured left border. It reads
og:site_name,og:title,og:description, and a thumbnail. Long descriptions are truncated hard at roughly two lines, so front-load the meaning. - LinkedIn uses a large image with the title beneath it and the domain in small caps. It ignores
og:descriptionentirely in the feed. If your title doesn't carry the message on its own, LinkedIn will not carry it for you. - Facebook renders the closest thing to the "reference" card: large image, title, description, domain. It is also the strictest about image dimensions and the most aggressive about caching.
- X/Twitter uses
twitter:cardto pick the layout and falls back to OG tags for content. Withouttwitter:card, you get the small square thumbnail rather than the large image. - iMessage and WhatsApp fetch the image directly from the client on some platforms rather than from a central scraper. An image behind a CDN rule that blocks unknown user agents will silently fail here while working everywhere else.
- Discord is the most forgiving and will fall back to an inline image if OG tags are missing entirely, which is why a link can look fine in Discord and bare everywhere else.
The practical consequence: write og:title so it stands alone, keep og:description under about 120 characters if you care about Slack, and always set twitter:card.
Twitter Card tags
Twitter (X) has its own set of meta tags that override OG tags when present:
<meta
name="twitter:card"
content="summary_large_image"
/>
<meta
name="twitter:title"
content="LintPage - Pre-Launch SEO Linting"
/>
<meta
name="twitter:description"
content="Catch SEO disasters before launch."
/>
The twitter:card value determines the preview layout:
summary- small square image on the left, title and description on the rightsummary_large_image- large image above the title and description (this is what you usually want)
If you don't set Twitter-specific tags, Twitter falls back to your OG tags. But setting twitter:card is important because there's no OG equivalent - without it, Twitter defaults to the small summary format.
Tagging blog posts and articles
For content pages, og:type: article unlocks a few extra fields that some platforms surface and that search engines read as corroborating signals:
<meta property="og:type" content="article" />
<meta property="article:published_time" content="2026-03-14T09:00:00.000Z" />
<meta property="article:modified_time" content="2026-08-29T11:20:00.000Z" />
<meta property="article:author" content="https://example.com/about" />
<meta property="article:section" content="SEO" />
Use ISO 8601 timestamps. article:modified_time should reflect a real content change - moving it on every deploy is the same fake-freshness signal that gets lastmod in your sitemap ignored.
In Next.js these map onto the openGraph object directly:
export const metadata: Metadata = {
openGraph: {
type: 'article',
publishedTime: '2026-03-14T09:00:00.000Z',
modifiedTime: '2026-08-29T11:20:00.000Z',
authors: ['https://example.com/about'],
},
};
Common mistakes
1. Forgetting og:type
While not required, og:type helps platforms understand your content:
<meta
property="og:type"
content="website"
/>
<!-- Or for blog posts -->
<meta
property="og:type"
content="article"
/>
2. Stale cached previews
Platforms cache link previews aggressively (sometimes for weeks). If you update your OG tags but the preview doesn't change, confirm the new tags are actually live with an og tag checker first - then use the platform's debug tool to force a refresh:
- Facebook: Sharing Debugger
- LinkedIn: Post Inspector
- Twitter/X: Card Validator was deprecated — post a test tweet or use a third-party preview tool instead
3. Missing og:site_name
This tag shows the site name in a smaller font above the title in some platforms:
<meta
property="og:site_name"
content="LintPage"
/>
It's a subtle branding touch that makes your shared links look more professional.
When the preview is wrong: check in this order
Most "my OG tags are broken" reports come down to one of five things, and they're worth ruling out in this sequence:
- Is the tag in the server-rendered HTML? View source, don't inspect the DOM. Tags injected by client-side JavaScript are invisible to every scraper - they do not run your JS.
- Is
og:imagean absolute URL? Relative paths silently produce no image. It must start withhttps://. - Is the image publicly reachable? Fetch it in a private window with no session. Scrapers have no cookies, and an image behind auth or a CDN bot rule will 403.
- Is the platform showing you a cached copy? Facebook and LinkedIn cache aggressively, sometimes for weeks. Run the platform's own debugger to force a refetch.
- Is something overriding your tags? Duplicate
og:tags, a CMS plugin, or a framework default can append a second set. Scrapers generally take the first occurrence, which may not be yours.
An og tag checker collapses the first three into one request by fetching the page the way a scraper does and reporting exactly what came back.
Setting up OG tags in Next.js
Next.js makes OG tags straightforward through the Metadata API:
export const metadata: Metadata = {
openGraph: {
title: 'My Page Title',
description: 'My page description',
url: 'https://example.com/my-page',
type: 'website',
siteName: 'My Site',
},
twitter: {
card: 'summary_large_image',
title: 'My Page Title',
description: 'My page description',
},
};
For dynamic OG images, create an opengraph-image.tsx file in your route directory. Next.js will automatically generate and serve the image, and add the appropriate meta tags.
Check your tags before sharing
The frustrating thing about OG tags is that you only discover they're broken after sharing a link publicly. By then, the ugly preview is already in the Slack channel or the tweet is live.
Run your URL through the LintPage Open Graph Checker before you share. It shows you exactly what your link preview will look like and flags any missing or misconfigured tags. Fix them before anyone sees the broken version.
OG tags are one slice of a larger set. The same <head> block holds your title, description, canonical, and robots directives, so it's worth reading how to write meta tags that actually improve your SEO and running a meta tag checker over the same page while you're there.