SKILL.md
Schema Markup
Schema earns rich results only when it is valid JSON-LD, matches what a human sees on the page, and uses a type Google still rewards. Markup that overstates the page is not optimization, it is a penalty risk.
Before You Write Markup
If a product marketing context file exists (.agents/product-marketing.md or .claude/product-marketing.md), read it first. Then confirm three things:
- Page type and primary content. That decides which types are even eligible.
- Existing markup. Never stack a second block of the same type on top of a broken one.
- Target rich result and its business value. No target, no markup.
Workflow
- Pick the type from the page, not the wishlist. Homepage: Organization plus WebSite. Blog post: Article. Product page: Product with offers. FAQ content: FAQPage. Tutorial: HowTo. Local page: LocalBusiness. Read references/schema-types.md for required and recommended properties per type plus full JSON-LD examples.
- Write JSON-LD only. Google's stated preference, easiest to maintain. Place in
<head>or end of<body>, in server-rendered HTML. Client-injected markup can be processed late or missed. - Combine types with `@graph` when one page needs Organization, WebSite, and BreadcrumbList together. One script block, one graph.
- Fill only facts the page displays. No fabricated ratings, no invented dates. Unknown values become
<FILL_ME>for the user, never guesses. - Validate before shipping. Rich Results Test for eligibility, validator.schema.org for syntax, Search Console Enhancements reports for ongoing monitoring.
Format Rules
- Dates in ISO 8601, URLs absolute and fully qualified.
- Enumerations exact:
https://schema.org/InStock, not"in stock". - One canonical block per type per page.
- Keep markup in sync with content. Stale schema after a redesign is a common silent failure.
Implementation by Stack
| Stack | Approach |
|---|---|
| Static HTML | JSON-LD in the template, partials for reusable blocks |
| React / Next.js | Schema component, server-side rendered, data serialized to JSON-LD |
| WordPress / CMS | Plugin (Yoast, Rank Math) or theme-level template edit |
Verification
Run the generated block through a JSON parser: python3 -c "import json,sys; json.load(sys.stdin)" < schema.json. Expect silent success. If it errors, fix the reported syntax issue (trailing comma, unquoted key) and re-run.
Then paste the page URL or code into the Rich Results Test (search.google.com/test/rich-results). Expect zero errors and the target rich result listed as detected. If errors appear, fix required properties first, warnings second, and retest before delivering.
Good vs Bad
Bad: Product page has no reviews yet. You add aggregateRating with a placeholder 4.8 so the stars show up. That is fabricated markup, a manual-action risk, and it will be your client's problem in six months.
Good: You skip rating markup, ship Product with real name, image, and offers, and note: "Add aggregateRating only after real reviews render on the page."
Footguns
- Markup for content that is not on the page. Google's top reason for structured data manual actions. Fix: every property value must be visible on the rendered page.
- Relative URLs.
"url": "/pricing"fails silently in some validators. Fix: absolute URLs everywhere. - JS-injected JSON-LD. SEO plugins and tag managers often inject schema client-side, so curl-based checks miss it and Google may process it late. Fix: move blocks into server-rendered HTML, especially Product and Offer.
- Duplicate conflicting blocks. A plugin block plus a hand-added block of the same type with different values. Fix: audit existing markup first, keep one source of truth.
Completion Checklist
- [ ] Type matches page content and current Google eligibility
- [ ] JSON parses clean, all URLs absolute, dates ISO 8601
- [ ] No fabricated values, unknowns marked
<FILL_ME> - [ ] Rich Results Test passes with zero errors
- [ ] Placement is server-rendered, one block per type
Any box unchecked: not done. Fix or say so.