Local preview of how a URL appears when shared on social networks. Extracts Open Graph, Twitter Cards, JSON-LD, validates structured data, and renders realistic CSS previews for Facebook, Twitter/X, and LinkedIn.
Built for debugging sites that are only accessible from a whitelisted IP — no external API calls, everything runs locally.
- Metadata extraction —
og:*,twitter:*,meta description, canonical URL, favicon, JSON-LD - Validation pipeline — unified
findings[]with severity taxonomy (ERROR / WARNING / INFO) and stable codes across OG, Twitter, JSON-LD, and general categories - Social preview cards — realistic CSS replicas of Facebook, Twitter/X, and LinkedIn link previews, each with platform‑specific styling
- JS rendering — optional Puppeteer/Chromium for SPAs with JS-injected meta tags; auto‑fallback when the HTTP scraper gets blocked (403)
- Screenshot — viewport screenshot captured when JS rendering is enabled; automatic cookie‑banner dismissal
- JSON-LD analysis — extract, flatten (
@graph), validate context, required fields, URL absolutes, dates, breadcrumb structure, duplicate type detection (opt‑in via toggle) - OG image validation — HEAD request check for reachability, file size, content type; icon/favicon pattern heuristic
- Favicon check — verifies declared favicon is reachable (HEAD request)
- Report — one‑click Markdown report with all findings, metadata, and JSON‑LD key summary (copy to clipboard)
- Dark mode — System / Light / Dark toggle, persisted in localStorage
- Tailwind v4 — modern CSS pipeline with automatic dark mode support
npm install
npm startOpens http://127.0.0.1:3333 in the browser.
| Command | Description |
|---|---|
npm start |
Build CSS + start server on 127.0.0.1:3333 and open browser |
npm run dev |
Server with --watch + Tailwind watcher in parallel; CSS rebuilt automatically |
npm run build:css |
Rebuild Tailwind CSS (after editing src/input.css) |
PORT=4444 npm start |
Custom port |
The server listens on 127.0.0.1 only (no external exposure).
{
"url": "https://example.com/page",
"jsRender": false,
"enableJsonLd": false
}jsRender: true— uses Puppeteer (headless Chromium). Useful for SPAs (React, Vue) that inject meta tags via JavaScript. Also used automatically as fallback when the HTTP scraper receives a 403/4xx.jsRender: false(default) — direct HTTP fetch + cheerio parsing (fast).enableJsonLd: true— enables JSON‑LD extraction and validation (off by default for performance).
Response:
{
"url": "https://example.com/page",
"meta": {
"og": {},
"twitter": {},
"general": {},
"jsonld": [],
"jldFindings": []
},
"previews": { "facebook": {}, "twitter": {}, "linkedin": {} },
"findings": [
{
"severity": "error|warning|info",
"category": "GENERAL|OG|TWITTER|JSONLD",
"code": "OG_IMAGE_MISSING",
"message": "Descrizione in italiano",
"field": "og:image"
}
],
"jsRender": false,
"screenshotUrl": null
}Findings are sorted by severity (ERROR → WARNING → INFO). Each finding has a stable code for filtering and regression tracking.
When jsRender: true, screenshotUrl contains a base64 JPEG of the page viewport.
├── server.mjs Express entrypoint
├── src/
│ ├── scraper.mjs Native HTTP fetch + gzip decompression + cheerio HTML parsing
│ ├── puppeteer.mjs Optional headless Chromium wrapper (cookie‑banner auto‑dismiss)
│ ├── previews.mjs Social preview card data builder
│ ├── opengraph.mjs OG + Twitter tag validation, image HEAD checks, favicon check
│ ├── jsonld.mjs JSON‑LD extraction, normalization, type validation, duplicate detection
│ ├── findings.mjs Severity/Category enums, sort utility
│ └── input.css Tailwind v4 source (→ public/style.css)
├── public/
│ ├── index.html Vanilla HTML UI
│ ├── app.js Frontend logic (findings rendering, report builder, theme management)
│ └── style.css Auto‑generated by Tailwind (do not edit)
├── Dockerfile Multi‑stage Docker build (with Chromium)
└── docker-compose.yml
HOST=0.0.0.0 docker compose up -d --buildThe container includes Chromium via apt, configured via PUPPETEER_EXECUTABLE_PATH. For external access, set HOST=0.0.0.0 (default is 127.0.0.1, local only).
For HTTPS, place behind a reverse proxy (Caddy, Nginx, Traefik).
The full taxonomy is defined in the project's findings.mjs and opengraph.mjs / jsonld.mjs modules. Key codes:
| Category | Code | Severity | Condition |
|---|---|---|---|
| GENERAL | TITLE_OG_TITLE_MISMATCH |
INFO | <title> and og:title differ |
| GENERAL | CANONICAL_OG_URL_MISMATCH |
WARNING | canonical and og:url point to different URLs |
| GENERAL | FAVICON_MISSING |
INFO | No <link rel="icon"> declared |
| GENERAL | FAVICON_UNREACHABLE |
WARNING | Favicon URL returns 4xx/5xx |
| OG | OG_TITLE_MISSING |
ERROR | og:title absent |
| OG | OG_IMAGE_MISSING |
ERROR | og:image absent |
| OG | OG_IMAGE_TOO_SMALL |
ERROR | Below 200×200 px |
| OG | OG_IMAGE_BELOW_RECOMMENDED |
WARNING | Below 1200×630 px |
| OG | OG_IMAGE_LOOKS_LIKE_ICON |
WARNING | URL suggests icon/favicon |
| OG | OG_IMAGE_UNREACHABLE |
ERROR | HEAD request failed |
| OG | OG_DUPLICATE_TAG |
ERROR | Same OG tag declared multiple times |
TWITTER_CARD_MISSING |
WARNING | twitter:card absent |
|
TWITTER_IMAGE_MISSING |
INFO | twitter:image absent, fallback on og:image |
|
| JSONLD | JSONLD_PARSE_ERROR |
ERROR | Block content is not valid JSON |
| JSONLD | JSONLD_TYPE_MISSING |
ERROR | Node has no @type |
| JSONLD | JSONLD_REQUIRED_FIELD_MISSING |
ERROR | Missing required field for type |
| JSONLD | JSONLD_DUPLICATE_TYPE |
WARNING | Same @type appears multiple times with incompatible structure |
| JSONLD | JSONLD_CONTEXT_NOT_SCHEMAORG |
WARNING | @context not schema.org (or uses http:// instead of https://) |
| JSONLD | JSONLD_CONTEXT_MISSING |
WARNING | Node has @type but no @context declared |
og:imagemust be ≥ 200×200 px; ≥ 1200×630 px recommended for full‑width Facebook previewsog:imageis the only image tag used by Facebook and LinkedIn;twitter:imageis X/Twitter only- If
og:titleis missing, the<title>HTML tag is used as fallback puppeteerdownloads Chromium (~300 MB) onnpm install. Remove it frompackage.jsonif not needed — JS rendering will show a clear error message- JSON‑LD analysis is opt‑in via the toggle in the UI (
enableJsonLd: falseby default) - When the HTTP scraper receives a 4xx response (bot‑blocking CDNs like CNN.com), the server automatically falls back to Puppeteer for that request