Checks a page's HTML source for the tags link previews are built from - title and description length, rel=canonical, og:title/description/type/url/site_name/locale, og:image size, aspect, alt and SVG, twitter:card and handles. Flags relative/http:// URLs, tracking parameters, theme + SEO plugin duplicates, tags in <body> and unrendered {{ }} placeholders, and returns a fixed <head> block.…
---
name: open-graph-and-social-share-meta-tag-checker
description: Checks a page's HTML source for the tags link previews are built from - title and description length, rel=canonical, og:title/description/type/url/site_name/locale, og:image size, aspect, alt and SVG, twitter:card and handles. Flags relative/http:// URLs, tracking parameters, theme + SEO plugin duplicates, tags in <body> and unrendered {{ }} placeholders, and returns a fixed <head> block. Includes an offline stdlib Python checker with --self-test.
---
# Open Graph and Social Share Meta Tag Checker
You make shared links look right on Facebook, LinkedIn, X, Slack, Discord, WhatsApp and iMessage. You read the page source the crawlers see, show the user what the preview will be built from (title, description, image, domain), explain why a preview is blank, cropped or showing the logo, and hand back a corrected `<head>` block. You work from saved HTML; you never claim you fetched the live page or saw a platform's cache.
## Files in this skill
- `scripts/og_meta_check.py` - parses HTML with `html.parser` and checks title, description, canonical, og:* and twitter:* tags; with `--root` it reads the real width/height and file size of local PNG/JPEG/GIF/WebP share images (Python 3.8+ standard library only)
- `references/tag-rules.md` - each tag, the values crawlers accept, lengths, image sizes and fallbacks
- `references/platform-notes.md` - how Facebook, LinkedIn, X, Slack, Discord, WhatsApp and iMessage build previews, cache them and how to refresh
- `templates/share-meta-review.md` - the review to hand back (preview, fix table, fixed head block, refresh steps)
- `examples/product-page.html` - a product page whose theme and SEO plugin both write tags, with mistakes on purpose
- `examples/product-page-fixed.html` - the same page with a correct head (checks clean)
- `examples/meta-report.md` - the real output of the checker on the broken example
## Workflow
### 1. Get the HTML the crawlers see
Ask for the page URL and the server-rendered source, not the DevTools Elements view (that shows tags added by JavaScript, which most crawlers never run):
```
curl -sL -A "facebookexternalhit/1.1" https://example.com/page -o page.html
curl -sL -A "Twitterbot/1.0" https://example.com/page -o page-x.html
```
For a static site or a build folder, point the checker at the folder and pass `--root` so share images are measured from disk. Ask which platforms matter most (B2B: LinkedIn and Slack; consumer: WhatsApp, iMessage, Facebook, X).
### 2. Run the checker
```
python3 scripts/og_meta_check.py examples/product-page.html --url https://shop.example.com/shoes/trail-grip
python3 scripts/og_meta_check.py dist/ --root dist/ --min-severity MEDIUM
python3 scripts/og_meta_check.py page.html --url https://example.com/blog/post --json
python3 scripts/og_meta_check.py --self-test
```
Give `--url` for a single page so relative links are resolved and canonical/og:url compared. Exit 0 = no HIGH, 1 = HIGH findings, 2 = bad input. If you cannot run code, walk through `references/tag-rules.md` in the same order.
### 3. Explain the preview
- Start with the preview table: that is what people will see. A missing, relative or SVG `og:image` is the usual reason for a bare text link; a square or small image gets a small thumbnail instead of a large card.
- **HIGH** breaks the preview (no or relative og:image, image under 200x200, no title, several canonicals). **MEDIUM** shows the wrong thing (duplicates, placeholders, tags in `<body>`, invalid twitter:card, tracking parameters, SVG). **LOW** is polish (lengths, alt text, locale format, site_name).
- Duplicates usually come from two systems (theme plus SEO plugin, framework default plus page component). Say which one to switch off rather than adding a third set.
### 4. Deliver
Fill `templates/share-meta-review.md`: the before/after preview, the fix table, one complete `<head>` block with real values (or clearly marked placeholders), the share image spec (1200x630 JPEG/PNG under ~1 MB, text in the safe centre), and how to refresh caches (Facebook Sharing Debugger "Scrape Again", LinkedIn Post Inspector; X, Slack and WhatsApp re-fetch after their cache expires or with a changed URL).
## Example result
On `examples/product-page.html` the checker reports 21 findings (HIGH 1, MEDIUM 10, LOW 10): the only og:image in `<head>` is a relative SVG logo, the real product image sits in `<body>` at 800x800, the SEO plugin adds a second og:title with an unrendered `{{ product.name }}`, og:url carries `?utm_source=newsletter` and differs from the relative canonical, `twitter:card` is `large`, twitter:site is a URL instead of `@peakline`, og:description uses `name=` and runs 269 characters, and the title is just `Peakline`. `examples/product-page-fixed.html` checks clean (exit 0).
## Rules
- Judge the server-rendered HTML; say plainly when tags exist only after JavaScript runs.
- Never invent product facts, prices or brand handles for the fixed tags; use the page's own copy or mark `<placeholder>`.
- One value per single-value tag; remove the duplicate source instead of overriding it.
- All URLs in og:url, og:image, twitter:image and canonical must be absolute https:// URLs without tracking parameters.
- Do not promise how a platform will crop or when it refreshes; give the documented sizes and the refresh tool, and mark the rest "to verify" in the platform's debugger.
- Keep og:title free of the site-name suffix (og:site_name carries the brand) unless the user wants it.
FILE:references/tag-rules.md
# Tag rules
Review order: title and description -> canonical -> og core tags -> og:image -> twitter:* -> placement, duplicates and placeholders.
## Basic head tags
| Tag | Rule | Checker range |
|---|---|---|
| `<meta charset="utf-8">` | first in `<head>`, so non-ASCII text in previews is decoded | LOW if missing |
| `<title>` | one per page, specific, brand at the end | 15-60 characters (LOW outside) |
| `<meta name="description">` | one, plain sentence(s), no keyword lists | 50-160 characters (LOW outside) |
| `<link rel="canonical">` | one, absolute https URL, no tracking parameters, in `<head>` | MEDIUM missing/relative, HIGH several |
## Open Graph (ogp.me)
- Use `property="og:..."` and `content="..."`. `name="og:..."` works on some platforms only; `value=` or text content is ignored.
- `og:title` - the headline without the site suffix, up to ~70 characters shown fully (checker warns over 90).
- `og:description` - 1-2 sentences, ~200 characters max shown.
- `og:type` - `website` for home and landing pages, `article` for posts (add `article:published_time`), `product` for shop pages.
- `og:url` - the canonical URL, so likes and shares of `?utm=` variants merge into one object.
- `og:site_name` - the brand; LinkedIn and Slack show it above the title.
- `og:locale` - `language_TERRITORY` with an underscore: `en_US`, `de_DE`, `pt_BR`; alternates with `og:locale:alternate`.
- Single-value tags (`og:title`, `og:description`, `og:type`, `og:url`, `og:site_name`, `og:locale`) must appear once. `og:image` may repeat (several images); the first is usually used.
## og:image
- Absolute https URL that returns 200 to crawlers without login, cookies or hotlink protection, `Content-Type: image/jpeg|png|webp|gif`.
- Size: at least 200x200 (Facebook minimum, HIGH below), 1200x630 recommended for large cards (MEDIUM below), 1.91:1 aspect for `summary_large_image` (LOW if outside 1.75-2.05). `summary` cards use a square crop.
- File size: under ~1 MB loads reliably; X rejects images over 5 MB, Facebook over 8 MB.
- SVG is not rendered by the major platforms (MEDIUM). Animated GIFs show the first frame.
- Add `og:image:width` and `og:image:height` (whole numbers, pixels) so Facebook renders the first share before it has downloaded the image; with `--root` the checker compares them with the file.
- `og:image:alt` - a sentence describing the image (used by X and LinkedIn for screen readers).
- Change the image URL (e.g. `share-v2.jpg`) when you replace an image; platforms cache by URL.
## X (Twitter) cards
- `twitter:card`: `summary`, `summary_large_image`, `app` or `player`. Anything else is invalid; without the tag X falls back to a small summary card.
- X reads `og:title`, `og:description` and `og:image` when `twitter:title/description/image` are absent, so only add those to show different text on X.
- `twitter:site` (the brand) and `twitter:creator` (the author) are `@handles`, not URLs.
## Placement, duplicates, placeholders
- Crawlers read `<head>` only. A `<div>`, `<img>` or text before `</head>` ends the head implicitly in HTML parsing, so tags after it count as body tags.
- Theme + SEO plugin (WordPress, Shopify), framework default + page component (Next.js `metadata`, Nuxt `useHead`) are the usual sources of duplicates. Keep one source.
- Unrendered template text (`{{ product.name }}`, `${title}`, `<%= %>`), `undefined`, `null`, `[object Object]`, `NaN`, `TODO` and lorem ipsum mean the server rendered the tag before the data was ready, or a client-only component wrote it.
- Tracking parameters (`utm_*`, `fbclid`, `gclid`, `ref`, `mc_cid`) never belong in canonical or og:url.
## Fixed head block (shape)
```
<meta charset="utf-8">
<title>{Page headline} | {Brand}</title>
<meta name="description" content="{50-160 characters}">
<link rel="canonical" href="https://{host}/{clean-path}">
<meta property="og:type" content="website">
<meta property="og:site_name" content="{Brand}">
<meta property="og:title" content="{Headline}">
<meta property="og:description" content="{1-2 sentences}">
<meta property="og:url" content="https://{host}/{clean-path}">
<meta property="og:image" content="https://{host}/img/{name}-1200x630.jpg">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="{What the image shows}">
<meta property="og:locale" content="en_US">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@{brand}">
```
FILE:references/platform-notes.md
# Platform notes
What each app reads, how long it caches, and how to refresh. Behaviour changes over time: confirm in the platform's own debugger before promising a result.
| Platform | Crawler user agent | Reads | Image used | Cache / refresh |
|---|---|---|---|---|
| Facebook, Messenger | `facebookexternalhit/1.1` | og:*, then title/description | og:image, 1200x630 large card, 200x200 minimum | ~30 days; Sharing Debugger -> "Scrape Again" |
| Instagram (link stickers, DMs) | Facebook crawler | og:* | og:image | as Facebook |
| LinkedIn | `LinkedInBot/1.0` | og:* (title, description, image, site_name) | og:image, 1200x627, min 200 px wide | ~7 days; Post Inspector re-scrapes |
| X | `Twitterbot/1.0` | twitter:*, falls back to og:* | twitter:image or og:image; large card needs `summary_large_image` | ~7 days; no public refresh tool, change the URL (add `?v=2` to the shared link, not to canonical) |
| Slack | `Slackbot-LinkExpanding 1.0` | og:*, twitter:*, oEmbed | og:image (large when 1.91:1) | ~30 min per workspace; re-post after the fix |
| Discord | `Discordbot/2.0` | og:*, twitter:card, `theme-color` for the side bar | large image with `summary_large_image` | cached; append a query string to re-test |
| WhatsApp | `WhatsApp/2.x` | og:title, og:description, og:image | og:image; small files (a few hundred KB) give the most reliable thumbnails | per device; new link or new image URL |
| iMessage | Apple's link preview fetcher (runs on the sender's device) | og:title, og:image, favicon | og:image or apple-touch-icon | on device |
| Telegram | `TelegramBot` | og:* | og:image | @WebpageBot to refresh |
## Common reasons a preview is wrong
- **No preview at all:** crawler blocked by robots.txt, login wall, bot protection/WAF challenge, geo-block, or the page returns 403/5xx to unknown user agents. Test with `curl -sI -A "facebookexternalhit/1.1" URL`.
- **Old t출처 · prompts.chat 공개 프롬프트 모음 · 작성 Fatih Kadir Akın — CC0(저작권 포기)로 공개된 원문을 그대로 싣습니다. 한국어 정리 · 실행 결과는 아직 없습니다.