Social Metadata Hardening Skill
Fix social sharing so every important URL unfurls as a rich card across all platforms.
When to Use
- Use when shared links show missing, stale, cropped, or incorrect previews on social and chat platforms.
- Use when auditing Open Graph, Twitter/X card, image URL, alt text, or
metadataBase coverage in a web app.
- Use before launch when every public page needs predictable rich previews across LinkedIn, X, Facebook, WhatsApp, Slack, Discord, and Telegram.
Why Previews Break
| Problem |
Root Cause |
| No preview at all |
Missing og:title, og:description, or og:image |
| Broken image |
Relative URL (must be absolute) |
| Wrong image size |
Image not 1200×630px (OG standard) |
| Plain text card |
Twitter card type missing or set to summary |
| Stale preview |
Platform caching old metadata |
| Metadata missing on crawl |
Tags added by client-side JS (crawlers don't run JS) |
The Gold Standard Metadata Block
Every shareable page needs ALL of these in static HTML:
// Next.js App Router — lib/socialMetadata.js
export function buildSocialMetadata({
title,
description,
path, // '/blog/my-post'
image, // '/images/og/my-post.jpg' or full URL
imageAlt,
imageWidth = 1200,
imageHeight = 630,
}) {
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || 'https://www.yourdomain.com';
// Always produce an absolute URL
const imageUrl = image?.startsWith('http') ? image : `${baseUrl}${image}`;
const pageUrl = `${baseUrl}${path}`;
// Detect MIME type from extension
const ext = imageUrl.split('.').pop().toLowerCase();
const mimeMap = { jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png', webp: 'image/webp' };
const imageType = mimeMap[ext] || 'image/jpeg';
return {
title,
description,
alternates: { canonical: pageUrl },
openGraph: {
title,
description,
url: pageUrl,
type: 'website', // use 'article' for blog posts
images: [{
url: imageUrl,
secureUrl: imageUrl, // explicit HTTPS version
width: imageWidth,
height: imageHeight,
alt: imageAlt || title,
type: imageType,
}],
},
twitter: {
card: 'summary_large_image', // NOT 'summary' — that shows a tiny image
title,
description,
images: [imageUrl],
},
};
}
Applying the Helper
Static page
// app/about/page.js
import { buildSocialMetadata } from '@/lib/socialMetadata';
export const metadata = buildSocialMetadata({
title: 'About Us | My Site',
description: 'Learn about our team and mission.',
path: '/about',
image: '/images/og/about.jpg',
imageAlt: 'The My Site team',
});
Dynamic page (blog post, tool page)
// app/blog/[slug]/page.js
import { buildSocialMetadata } from '@/lib/socialMetadata';
export async function generateMetadata({ params }) {
const post = await getPost(params.slug);
return buildSocialMetadata({
title: `${post.title} | My Blog`,
description: post.excerpt,
path: `/blog/${params.slug}`,
image: post.ogImage || '/images/og/default.jpg',
imageAlt: post.title,
});
}
Homepage (app/layout.js or app/page.js)
export const metadata = {
metadataBase: new URL('https://www.yourdomain.com'), // REQUIRED for absolute URLs
...buildSocialMetadata({
title: 'My Site — Tagline Here',
description: 'Site-wide description.',
path: '/',
image: '/images/og/home.jpg',
}),
};
⚠️ Set metadataBase when using relative metadata URLs. If your helper already outputs absolute canonical/OG URLs, previews can still work without it.
OG Image Checklist
Good OG images:
- 1200 × 630px (2:1 ratio — works on all platforms)
- Under 8MB (Facebook limit)
- Served over HTTPS
- File name has no spaces (use hyphens)
- Format: JPEG or PNG (WebP works on most but not all crawlers)
- Accessible via GET with no authentication
# Verify your OG image is reachable and correct size
curl -sI https://www.yourdomain.com/images/og/home.jpg | grep -i "content-type\|content-length\|status"
Platform-Specific Notes
Facebook / Meta
- Caches aggressively — use the Sharing Debugger to force recrawl
- Minimum image: 200×200px (but use 1200×630 for quality)
- Needs:
og:title, og:description, og:image, og:url
X / Twitter
- Use
twitter:card = summary_large_image for full-width images
twitter:image must be an absolute URL
- Use the Card Validator to test
LinkedIn
- Caches hard — use Post Inspector to refresh
- Respects
og: tags; ignores twitter: tags
- Image must be ≥1.91:1 aspect ratio
WhatsApp / Telegram
- Read OG tags on first share; cache can last hours
- Re-share after a few hours for the cache to clear naturally
Slack / Discord
- Both use OG tags; both cache
- Discord also supports
og:type = article for richer embeds
Debugging Social Previews
1. Check raw HTML for tags
curl -s https://www.yourdomain.com/blog/my-post | grep -i "og:\|twitter:"
If tags don't appear → they're being added by JavaScript (not crawlable). Fix: move to export const metadata or generateMetadata.
2. Validate with platform tools
3. Force cache refresh
After deploying fixes, paste the URL into each platform's debugger and click "Fetch new scrape information" (or equivalent).
Social Metadata Checklist
Limitations
- Cannot force immediate cache refresh on every social platform; some previews may remain stale after a correct fix.
- Requires publicly reachable deployed URLs for reliable validation with platform debuggers.
- Does not replace brand, accessibility, or legal review of image text, alt text, and preview copy.
1---2name: social-metadata-hardening3description: Fix social sharing previews so URLs render as rich cards on Facebook, LinkedIn, X/Twitter, WhatsApp, Telegram, and more. Covers OG tags, Twitter cards, absolute image URLs, and debugging.4license: MIT5---67# Social Metadata Hardening Skill89Fix social sharing so every important URL unfurls as a rich card across all platforms.1011---1213## When to Use1415- Use when shared links show missing, stale, cropped, or incorrect previews on social and chat platforms.16- Use when auditing Open Graph, Twitter/X card, image URL, alt text, or `metadataBase` coverage in a web app.17- Use before launch when every public page needs predictable rich previews across LinkedIn, X, Facebook, WhatsApp, Slack, Discord, and Telegram.1819---2021## Why Previews Break2223| Problem | Root Cause |24|---------|-----------|25| No preview at all | Missing og:title, og:description, or og:image |26| Broken image | Relative URL (must be absolute) |27| Wrong image size | Image not 1200×630px (OG standard) |28| Plain text card | Twitter card type missing or set to `summary` |29| Stale preview | Platform caching old metadata |30| Metadata missing on crawl | Tags added by client-side JS (crawlers don't run JS) |3132---3334## The Gold Standard Metadata Block3536Every shareable page needs ALL of these in static HTML:3738```js39// Next.js App Router — lib/socialMetadata.js40export function buildSocialMetadata({41 title,42 description,43 path, // '/blog/my-post'44 image, // '/images/og/my-post.jpg' or full URL45 imageAlt,46 imageWidth = 1200,47 imageHeight = 630,48}) {49 const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || 'https://www.yourdomain.com';50 51 // Always produce an absolute URL52 const imageUrl = image?.startsWith('http') ? image : `${baseUrl}${image}`;53 const pageUrl = `${baseUrl}${path}`;54 55 // Detect MIME type from extension56 const ext = imageUrl.split('.').pop().toLowerCase();57 const mimeMap = { jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png', webp: 'image/webp' };58 const imageType = mimeMap[ext] || 'image/jpeg';5960 return {61 title,62 description,63 alternates: { canonical: pageUrl },64 openGraph: {65 title,66 description,67 url: pageUrl,68 type: 'website', // use 'article' for blog posts69 images: [{70 url: imageUrl,71 secureUrl: imageUrl, // explicit HTTPS version72 width: imageWidth,73 height: imageHeight,74 alt: imageAlt || title,75 type: imageType,76 }],77 },78 twitter: {79 card: 'summary_large_image', // NOT 'summary' — that shows a tiny image80 title,81 description,82 images: [imageUrl],83 },84 };85}86```8788---8990## Applying the Helper9192### Static page93```js94// app/about/page.js95import { buildSocialMetadata } from '@/lib/socialMetadata';9697export const metadata = buildSocialMetadata({98 title: 'About Us | My Site',99 description: 'Learn about our team and mission.',100 path: '/about',101 image: '/images/og/about.jpg',102 imageAlt: 'The My Site team',103});104```105106### Dynamic page (blog post, tool page)107```js108// app/blog/[slug]/page.js109import { buildSocialMetadata } from '@/lib/socialMetadata';110111export async function generateMetadata({ params }) {112 const post = await getPost(params.slug);113 return buildSocialMetadata({114 title: `${post.title} | My Blog`,115 description: post.excerpt,116 path: `/blog/${params.slug}`,117 image: post.ogImage || '/images/og/default.jpg',118 imageAlt: post.title,119 });120}121```122123### Homepage (app/layout.js or app/page.js)124```js125export const metadata = {126 metadataBase: new URL('https://www.yourdomain.com'), // REQUIRED for absolute URLs127 ...buildSocialMetadata({128 title: 'My Site — Tagline Here',129 description: 'Site-wide description.',130 path: '/',131 image: '/images/og/home.jpg',132 }),133};134```135136> ⚠️ **Set `metadataBase` when using relative metadata URLs.** If your helper already outputs absolute canonical/OG URLs, previews can still work without it.137138---139140## OG Image Checklist141142Good OG images:143- **1200 × 630px** (2:1 ratio — works on all platforms)144- **Under 8MB** (Facebook limit)145- Served over **HTTPS**146- File name has **no spaces** (use hyphens)147- Format: **JPEG or PNG** (WebP works on most but not all crawlers)148- **Accessible via GET** with no authentication149150```bash151# Verify your OG image is reachable and correct size152curl -sI https://www.yourdomain.com/images/og/home.jpg | grep -i "content-type\|content-length\|status"153```154155---156157## Platform-Specific Notes158159### Facebook / Meta160- Caches aggressively — use the [Sharing Debugger](https://developers.facebook.com/tools/debug/) to force recrawl161- Minimum image: 200×200px (but use 1200×630 for quality)162- Needs: `og:title`, `og:description`, `og:image`, `og:url`163164### X / Twitter165- Use `twitter:card = summary_large_image` for full-width images166- `twitter:image` must be an absolute URL167- Use the [Card Validator](https://cards-dev.twitter.com/validator) to test168169### LinkedIn170- Caches hard — use [Post Inspector](https://www.linkedin.com/post-inspector/) to refresh171- Respects `og:` tags; ignores `twitter:` tags172- Image must be ≥1.91:1 aspect ratio173174### WhatsApp / Telegram175- Read OG tags on first share; cache can last hours176- Re-share after a few hours for the cache to clear naturally177178### Slack / Discord179- Both use OG tags; both cache180- Discord also supports `og:type = article` for richer embeds181182---183184## Debugging Social Previews185186### 1. Check raw HTML for tags187```bash188curl -s https://www.yourdomain.com/blog/my-post | grep -i "og:\|twitter:"189```190If tags don't appear → they're being added by JavaScript (not crawlable). Fix: move to `export const metadata` or `generateMetadata`.191192### 2. Validate with platform tools193194| Platform | Tool |195|----------|------|196| Facebook | https://developers.facebook.com/tools/debug/ |197| LinkedIn | https://www.linkedin.com/post-inspector/ |198| Twitter/X | https://cards-dev.twitter.com/validator |199| General | https://metatags.io |200201### 3. Force cache refresh202After deploying fixes, paste the URL into each platform's debugger and click "Fetch new scrape information" (or equivalent).203204---205206## Social Metadata Checklist207208- [ ] `metadataBase` set in root layout209- [ ] All shareable pages use shared `buildSocialMetadata` helper210- [ ] OG image URLs are absolute (start with `https://`)211- [ ] `secureUrl` set equal to `url` in OG image block212- [ ] Image is 1200×630px, under 8MB, HTTPS213- [ ] `twitter:card` is `summary_large_image` (not `summary`)214- [ ] Image alt text present215- [ ] Tags visible in raw HTML (not JavaScript-rendered)216- [ ] All platform debuggers show correct preview217- [ ] Cache refreshed on all platforms after deployment218219## Limitations220221- Cannot force immediate cache refresh on every social platform; some previews may remain stale after a correct fix.222- Requires publicly reachable deployed URLs for reliable validation with platform debuggers.223- Does not replace brand, accessibility, or legal review of image text, alt text, and preview copy.