social-metadata-hardening
How to Install
git clone https://github.com/self && cp skills/social-metadata-hardening ~/.claude/skills/Copy SKILL.md into your .cursorrules fileSocial 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
metadataBasecoverage 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
metadataBasewhen 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_imagefor full-width images twitter:imagemust be an absolute URL- Use the Card Validator to test
- Caches hard — use Post Inspector to refresh
- Respects
og:tags; ignorestwitter: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 = articlefor 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
| Platform | Tool |
|---|---|
| https://developers.facebook.com/tools/debug/ | |
| https://www.linkedin.com/post-inspector/ | |
| Twitter/X | https://cards-dev.twitter.com/validator |
| General | https://metatags.io |
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
- [ ]
metadataBaseset in root layout - [ ] All shareable pages use shared
buildSocialMetadatahelper - [ ] OG image URLs are absolute (start with
https://) - [ ]
secureUrlset equal tourlin OG image block - [ ] Image is 1200×630px, under 8MB, HTTPS
- [ ]
twitter:cardissummary_large_image(notsummary) - [ ] Image alt text present
- [ ] Tags visible in raw HTML (not JavaScript-rendered)
- [ ] All platform debuggers show correct preview
- [ ] Cache refreshed on all platforms after deployment
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.
Details
| Category | Content → SEO |
| Source | self |
| Stars | N/A |
| Risk Level | Safe |