@seoagent-official/seoagent 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +153 -0
- package/index.js +20 -0
- package/package.json +37 -0
- package/postinstall-hint.cjs +31 -0
- package/skills/references/audit-checks.md +228 -0
- package/skills/references/keyword-research.md +165 -0
- package/skills/references/landing-pages.md +164 -0
- package/skills/references/long-tail-articles.md +126 -0
- package/skills/references/pillar-articles.md +127 -0
- package/skills/references/programmatic.md +155 -0
- package/skills/references/rewrite-protocol.md +163 -0
- package/skills/references/schema-markup.md +297 -0
- package/skills/references/sub-pillar-articles.md +114 -0
- package/skills/seoagent.md +561 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# Programmatic SEO Protocol
|
|
2
|
+
|
|
3
|
+
Programmatic SEO is template-driven scale: one template + a dataset → hundreds or thousands of pages targeting long-tail keyword variations. Done well, it captures real search demand at scale. Done badly, it's thin-content spam that gets penalized.
|
|
4
|
+
|
|
5
|
+
## Core Rules (apply to every programmatic project)
|
|
6
|
+
|
|
7
|
+
1. **Every page must provide unique value.** Just swapping `{city}` in a template is thin content. Each page needs at least one piece of data that *only* this page can show.
|
|
8
|
+
2. **Proprietary data > public data.** "Best restaurants in {city}" using public Google data → already done. "Average response time of plumbers in {city}, based on our 50,000-job dataset" → unique.
|
|
9
|
+
3. **Internal linking prevents orphaning.** Every programmatic page must link to at least 3 other programmatic pages in the same set + 1 hub page.
|
|
10
|
+
4. **Match real search intent.** Run WebSearch on 5-10 sample queries before building. If the SERP is dominated by aggregators or directories, you may have a shot. If it's dominated by Wikipedia or government sites, skip.
|
|
11
|
+
5. **Quality > quantity.** 200 great pages beat 5,000 thin ones. Google's site-quality signals are aggregated — thin programmatic pages drag down your whole domain.
|
|
12
|
+
|
|
13
|
+
## The 12 Programmatic Playbooks
|
|
14
|
+
|
|
15
|
+
### 1. Comparison Pages
|
|
16
|
+
Pattern: `/{tool-a}-vs-{tool-b}` (e.g. `/notion-vs-airtable`)
|
|
17
|
+
Data needed: features matrix, pricing, use cases, reviews per tool
|
|
18
|
+
Search intent: high-intent, decision stage
|
|
19
|
+
Word count: 1500-2500
|
|
20
|
+
Schema: `Product` per tool + `ItemList` of comparison points
|
|
21
|
+
Example: G2's comparison pages, Capterra, Webflow's "Webflow vs X" pages
|
|
22
|
+
|
|
23
|
+
### 2. Alternative Pages
|
|
24
|
+
Pattern: `/{tool}-alternatives` (e.g. `/zapier-alternatives`)
|
|
25
|
+
Data needed: list of 5-15 alternatives with key differentiators per
|
|
26
|
+
Search intent: competitor research
|
|
27
|
+
Word count: 2000-3000
|
|
28
|
+
Schema: `ItemList` with each alternative as a `Product`
|
|
29
|
+
Critical: don't just list competitors; explain WHY someone should choose each one
|
|
30
|
+
|
|
31
|
+
### 3. Use Case Pages
|
|
32
|
+
Pattern: `/{tool}-for-{use-case}` (e.g. `/airtable-for-project-management`)
|
|
33
|
+
Data needed: tool capabilities, use-case-specific workflow, sample template
|
|
34
|
+
Search intent: evaluating fit
|
|
35
|
+
Word count: 1200-1800
|
|
36
|
+
Best when: you have a real example or template to share
|
|
37
|
+
|
|
38
|
+
### 4. Integration Pages
|
|
39
|
+
Pattern: `/integrations/{tool}` or `/{your-tool}-{their-tool}-integration`
|
|
40
|
+
Data needed: setup steps, common workflows, example automations
|
|
41
|
+
Search intent: people Google "{your tool} {other tool}" to verify integration exists
|
|
42
|
+
Word count: 800-1200
|
|
43
|
+
Schema: `HowTo` for the setup walkthrough
|
|
44
|
+
Each integration needs: setup steps, common workflows, an FAQ on quirks
|
|
45
|
+
|
|
46
|
+
### 5. Location Pages
|
|
47
|
+
Pattern: `/{service}-{city}` or `/{tool}/cities/{city}`
|
|
48
|
+
Data needed: location-specific stats, local examples, location-relevant pricing
|
|
49
|
+
Search intent: local-intent searches ("plumber chicago")
|
|
50
|
+
Word count: 600-1000
|
|
51
|
+
Critical: needs Local Business schema with address, geo coordinates, hours
|
|
52
|
+
Risk: highest spam risk — make sure each page genuinely has unique local content
|
|
53
|
+
|
|
54
|
+
### 6. Industry / Persona Pages
|
|
55
|
+
Pattern: `/for/{industry}` or `/{tool}-for-{persona}`
|
|
56
|
+
Data needed: industry-specific use cases, customers in that vertical, ROI metrics specific to that vertical
|
|
57
|
+
Search intent: SaaS evaluation by industry
|
|
58
|
+
Word count: 1000-1500
|
|
59
|
+
|
|
60
|
+
### 7. Glossary Entries
|
|
61
|
+
Pattern: `/glossary/{term}` or `/{topic}/{term}`
|
|
62
|
+
Data needed: clear definition, examples, related terms
|
|
63
|
+
Search intent: informational, AI-search-friendly
|
|
64
|
+
Word count: 400-800 (shorter than long_tails)
|
|
65
|
+
Schema: `DefinedTerm` + `Article`
|
|
66
|
+
Build at scale once: 50-100 terms in your domain's vocabulary
|
|
67
|
+
|
|
68
|
+
### 8. Listing / Directory Pages
|
|
69
|
+
Pattern: `/{category}/{location-or-attribute}` (e.g. `/best-{tool}-for-{use-case}`)
|
|
70
|
+
Data needed: curated list of items with structured data per item
|
|
71
|
+
Search intent: "best X" searches
|
|
72
|
+
Word count: 1500-3000 (mostly structured)
|
|
73
|
+
Schema: `ItemList`
|
|
74
|
+
|
|
75
|
+
### 9. Calculator / Tool Pages
|
|
76
|
+
Pattern: `/{topic}-calculator` or `/{tool}-calculator`
|
|
77
|
+
Data needed: a working calculator + explanatory content
|
|
78
|
+
Search intent: high — the calculator IS the value
|
|
79
|
+
Word count: 600-1000 of explainer copy alongside the tool
|
|
80
|
+
Best programmatic format: the tool itself is unique value per page
|
|
81
|
+
|
|
82
|
+
### 10. Template / Example Pages
|
|
83
|
+
Pattern: `/templates/{template-slug}`
|
|
84
|
+
Data needed: a downloadable or copyable template + use case explanation
|
|
85
|
+
Search intent: people Googling "{x} template"
|
|
86
|
+
Word count: 500-1000
|
|
87
|
+
Schema: `CreativeWork` or specific subtype
|
|
88
|
+
|
|
89
|
+
### 11. Profile / Listing-of-Things Pages
|
|
90
|
+
Pattern: `/companies/{company}` or `/people/{person}` or `/products/{product}`
|
|
91
|
+
Data needed: structured profile with proprietary data
|
|
92
|
+
Search intent: branded searches for individual entities
|
|
93
|
+
Word count: 800-1500 with structured data carrying weight
|
|
94
|
+
Schema: `Organization`, `Person`, or `Product` as appropriate
|
|
95
|
+
|
|
96
|
+
### 12. Translated Pages
|
|
97
|
+
Pattern: `/{lang}/{slug}` or subdomain
|
|
98
|
+
Data needed: properly translated content (not machine-translated boilerplate)
|
|
99
|
+
Search intent: language-specific search demand
|
|
100
|
+
Critical: machine translation is detected by Google. Use professional translation or skip.
|
|
101
|
+
Schema: hreflang tags + canonical
|
|
102
|
+
|
|
103
|
+
## Choosing a Playbook
|
|
104
|
+
|
|
105
|
+
Match the playbook to your assets:
|
|
106
|
+
- **Have unique data?** → Listing pages (#8), Profile pages (#11), Comparison pages (#1)
|
|
107
|
+
- **Have a tool?** → Calculator pages (#9), Integration pages (#4)
|
|
108
|
+
- **Have customers in many segments?** → Industry pages (#6), Use case pages (#3)
|
|
109
|
+
- **Have many integrations?** → Integration pages (#4)
|
|
110
|
+
- **Have geographic relevance?** → Location pages (#5) (with caution)
|
|
111
|
+
|
|
112
|
+
You can combine playbooks: comparison pages × industry → `/{tool-a}-vs-{tool-b}-for-{industry}`. Be selective — combinatorial explosions create thin content fast.
|
|
113
|
+
|
|
114
|
+
## Implementation Framework
|
|
115
|
+
|
|
116
|
+
1. **Keyword Pattern Research**
|
|
117
|
+
- Run WebSearch on 5-10 sample queries that fit the pattern
|
|
118
|
+
- Identify search volume signals (autocomplete, "people also ask")
|
|
119
|
+
- Confirm the SERP isn't dominated by Wikipedia or government sites
|
|
120
|
+
2. **Data Source**
|
|
121
|
+
- Identify your data source. Is it proprietary? Public-but-aggregated? User-generated?
|
|
122
|
+
- Each page needs at least one *unique* data point
|
|
123
|
+
3. **Template Design**
|
|
124
|
+
- Mock up the template with a real example. Is it 600+ words of actual content per page?
|
|
125
|
+
- Design the URL pattern. Flat is better than nested.
|
|
126
|
+
4. **Internal Linking Strategy**
|
|
127
|
+
- Plan the hub page (a category index)
|
|
128
|
+
- Plan how programmatic pages link to each other (related, alternatives)
|
|
129
|
+
5. **Build a Pilot Set**
|
|
130
|
+
- Build 10-20 pages first. Wait 4-8 weeks. Check rankings.
|
|
131
|
+
- If those rank, scale up. If they don't, diagnose before building 1,000.
|
|
132
|
+
6. **Sitemap & Indexation**
|
|
133
|
+
- Submit programmatic pages in a separate sitemap so you can monitor indexation rate
|
|
134
|
+
- If <50% are indexed after 8 weeks, you have a quality problem — Google is rejecting them
|
|
135
|
+
7. **Monitor and Prune**
|
|
136
|
+
- Pages ranking on positions 50+ after 6 months are dragging down site quality
|
|
137
|
+
- Either improve them or `noindex` them
|
|
138
|
+
|
|
139
|
+
## When NOT to Build Programmatic
|
|
140
|
+
|
|
141
|
+
- The SERP is owned by Wikipedia, government, or huge brands → skip
|
|
142
|
+
- You don't have unique data → skip (or get unique data first)
|
|
143
|
+
- Your dataset has gaps that produce empty/thin pages → tighten the dataset first
|
|
144
|
+
- Your domain is new (< 6 months old) → focus on traditional content first
|
|
145
|
+
|
|
146
|
+
## After Building
|
|
147
|
+
|
|
148
|
+
1. Add a hub page that lists all programmatic pages with internal linking
|
|
149
|
+
2. Submit a dedicated sitemap to Google Search Console
|
|
150
|
+
3. Track indexation rate weekly for the first 8 weeks
|
|
151
|
+
4. Persist the pattern to `.seoagent/strategy/clusters/programmatic-{pattern}.md` so the agent knows about it next session
|
|
152
|
+
|
|
153
|
+
## Persistence
|
|
154
|
+
|
|
155
|
+
Programmatic pages get saved to `.seoagent/content/programmatic/{slug}.md` with `page_type: programmatic`. The cluster file in `strategy/clusters/` tracks the pattern (template) and the URL list.
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Rewrite & Refresh Protocol (Phase 4b)
|
|
2
|
+
|
|
3
|
+
When to rewrite an existing article instead of writing a new one. The single most-asked feature in SEO content workflows: "update my existing post."
|
|
4
|
+
|
|
5
|
+
## When to Rewrite
|
|
6
|
+
|
|
7
|
+
Strong signals an article needs a refresh:
|
|
8
|
+
|
|
9
|
+
1. **Stats / facts > 18 months old** — readers and search engines penalize stale data
|
|
10
|
+
2. **Ranking dropped** — used to be on page 1, now on page 3+
|
|
11
|
+
3. **Search intent shifted** — Google's SERP for the keyword now shows different content types
|
|
12
|
+
4. **Competitor content is now stronger** — newer competitors cover what you missed
|
|
13
|
+
5. **Cluster expanded** — new sub_pillars exist that the pillar should reference
|
|
14
|
+
6. **Tone drift** — the article doesn't match the current `context.md` brand voice
|
|
15
|
+
7. **User explicitly asks** — "rewrite this", "update this post", "refresh the homepage"
|
|
16
|
+
|
|
17
|
+
If none of these are true: don't rewrite. Refreshing for the sake of it can hurt rankings (Google sees disruptive changes to ranking pages).
|
|
18
|
+
|
|
19
|
+
## Procedure
|
|
20
|
+
|
|
21
|
+
### Step 1: Identify the Target
|
|
22
|
+
|
|
23
|
+
If the user gives a slug, file path, or URL:
|
|
24
|
+
- Read `.seoagent/content/{slug}.md` if it's a SEOAgent-generated article
|
|
25
|
+
- WebFetch the live URL if it's not in `.seoagent/`
|
|
26
|
+
- If neither — ask the user where the source of truth is
|
|
27
|
+
|
|
28
|
+
### Step 2: Read Context
|
|
29
|
+
|
|
30
|
+
Before any edits, read:
|
|
31
|
+
- `.seoagent/context.md` — current brand voice, banned topics, audience
|
|
32
|
+
- `.seoagent/strategy/clusters/{cluster}.md` — the article's role and link graph
|
|
33
|
+
- `.seoagent/briefs/{slug}.md` if it exists — the original brief
|
|
34
|
+
|
|
35
|
+
### Step 3: Diagnose the Gaps
|
|
36
|
+
|
|
37
|
+
Run a structured diagnosis. Use this exact template — output it to the user before editing:
|
|
38
|
+
|
|
39
|
+
```markdown
|
|
40
|
+
## 🔍 Refresh Diagnosis — {slug}
|
|
41
|
+
|
|
42
|
+
### Stale Content
|
|
43
|
+
- {stat or fact} — currently says "X (2024 data)", should be updated
|
|
44
|
+
- {section} — references discontinued tool / outdated framework
|
|
45
|
+
|
|
46
|
+
### Missing Coverage
|
|
47
|
+
- New sub_pillar exists in cluster: {sub_pillar} — pillar should link to it
|
|
48
|
+
- Top-3 competitor now covers {topic}; we don't
|
|
49
|
+
|
|
50
|
+
### Structural Issues
|
|
51
|
+
- {observation about hierarchy, AI extractability, etc.}
|
|
52
|
+
|
|
53
|
+
### Tone & Voice
|
|
54
|
+
- {observation if context.md has shifted since article was written}
|
|
55
|
+
|
|
56
|
+
### What's Working (Preserve)
|
|
57
|
+
- {section} ranks well; keep largely intact
|
|
58
|
+
- {section} has good backlinks per current analysis
|
|
59
|
+
|
|
60
|
+
## Plan (Y/N)
|
|
61
|
+
1. Update {N} stats with 2026 figures
|
|
62
|
+
2. Add new H2: "{section title}" linking to {sub_pillar}
|
|
63
|
+
3. Tighten {section} — currently {old word count}, target {new word count}
|
|
64
|
+
4. Refresh hero image alt text + add OG card
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Wait for user confirmation before executing.
|
|
68
|
+
|
|
69
|
+
### Step 4: Execute the Rewrite
|
|
70
|
+
|
|
71
|
+
Rules:
|
|
72
|
+
- **Preserve the URL slug.** Never change `slug` — even if the title changes, the URL stays.
|
|
73
|
+
- **Preserve sections that rank.** If a section is the page's strongest signal, keep its core wording.
|
|
74
|
+
- **Use `Edit`, not `Write`.** Edit one section at a time so changes are reviewable.
|
|
75
|
+
- **Update `dateModified`** in JSON-LD. Don't change `datePublished` — that resets ranking signal.
|
|
76
|
+
- **Bump `version`** in frontmatter (1 → 2 → 3).
|
|
77
|
+
|
|
78
|
+
### Step 5: Update the Frontmatter
|
|
79
|
+
|
|
80
|
+
```yaml
|
|
81
|
+
---
|
|
82
|
+
slug: tech-seo-guide
|
|
83
|
+
page_type: pillar
|
|
84
|
+
title: "The Complete Technical SEO Guide for 2026" # year may update
|
|
85
|
+
status: drafted
|
|
86
|
+
created_at: 2024-01-15T10:00:00Z # NEVER change
|
|
87
|
+
updated_at: 2026-04-27T10:00:00Z # update this
|
|
88
|
+
version: 3 # bump
|
|
89
|
+
word_count: 3450 # update if changed
|
|
90
|
+
---
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Add to JSON-LD:
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"@type": "Article",
|
|
97
|
+
"datePublished": "2024-01-15",
|
|
98
|
+
"dateModified": "2026-04-27"
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Both dates in the schema. Google uses `dateModified` to know freshness without resetting ranking signal.
|
|
103
|
+
|
|
104
|
+
### Step 6: Update Internal Links
|
|
105
|
+
|
|
106
|
+
If the rewrite added or changed internal links:
|
|
107
|
+
- Update the cluster file's "Internal Linking" section
|
|
108
|
+
- If you added a link DOWN to a sub_pillar, also add the reverse link UP from the sub_pillar (use `Edit`)
|
|
109
|
+
|
|
110
|
+
### Step 7: Log and Sync
|
|
111
|
+
|
|
112
|
+
Append to `.seoagent/changelog.md`:
|
|
113
|
+
```
|
|
114
|
+
[2026-04-27] Rewrote tech-seo-guide v3: updated 7 stats, added "AI Search Readiness" H2 linking to ai-search-readiness sub_pillar, tightened from 3120 → 3450 words
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Run `seoagent sync`.
|
|
118
|
+
|
|
119
|
+
## Special Cases
|
|
120
|
+
|
|
121
|
+
### Rewriting a Landing Page
|
|
122
|
+
|
|
123
|
+
Same protocol but:
|
|
124
|
+
- Track conversion impact — note in changelog if there's a CRO concern
|
|
125
|
+
- Don't ship the rewrite during a campaign in flight — coordinate with the user
|
|
126
|
+
- Update OG card and Twitter card alt text (often forgotten)
|
|
127
|
+
- Update JSON-LD `Product` / `Offer` if pricing or feature claims changed
|
|
128
|
+
|
|
129
|
+
### Rewriting Without an Existing Brief
|
|
130
|
+
|
|
131
|
+
If the article exists but `.seoagent/briefs/{slug}.md` does not:
|
|
132
|
+
1. Generate a brief from the live article first (Phase 3 protocol)
|
|
133
|
+
2. Show it to the user, confirm it represents the *intended* article
|
|
134
|
+
3. Then rewrite against that brief
|
|
135
|
+
|
|
136
|
+
This catches situations where the article drifted from any original spec.
|
|
137
|
+
|
|
138
|
+
### "Annual Refresh" — Updating Year References
|
|
139
|
+
|
|
140
|
+
If the user says "update for 2026" and only the year needs to change:
|
|
141
|
+
1. Find every `2024`, `2025` reference in the article
|
|
142
|
+
2. Update only the ones that genuinely refer to current-year data
|
|
143
|
+
3. Update `dateModified` in JSON-LD
|
|
144
|
+
4. Don't bump major version — this is a minor refresh
|
|
145
|
+
|
|
146
|
+
Output a brief diff summary so the user can confirm nothing else changed.
|
|
147
|
+
|
|
148
|
+
### Rewriting AI-Generated Content That Wasn't Yours
|
|
149
|
+
|
|
150
|
+
If the user wants to rewrite a post that wasn't drafted with SEOAgent (no brief, no `.seoagent/content/{slug}.md`):
|
|
151
|
+
1. WebFetch the live URL
|
|
152
|
+
2. Save a copy to `.seoagent/content/{slug}.md` with `imported_at` in frontmatter
|
|
153
|
+
3. Generate a brief representing the *current* article
|
|
154
|
+
4. Then run the diagnosis (Step 3) and rewrite
|
|
155
|
+
|
|
156
|
+
This brings the article into SEOAgent's persistence model so future refreshes have history.
|
|
157
|
+
|
|
158
|
+
## Common Pitfalls
|
|
159
|
+
|
|
160
|
+
- **Changing the URL.** Breaks backlinks, breaks rankings. Use a 301 redirect only if absolutely necessary, never silently.
|
|
161
|
+
- **Resetting `datePublished`.** Tells Google "this is a new article" — kills accumulated ranking signal.
|
|
162
|
+
- **Rewriting the entire article.** A 90% rewrite is a new article. If you're doing that, change the slug too — but accept the ranking reset.
|
|
163
|
+
- **Forgetting the link graph.** A pillar rewrite without updating sub_pillar links creates broken hub-and-spoke structure.
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
# Schema Markup Library
|
|
2
|
+
|
|
3
|
+
JSON-LD is the recommended format. Embed in `<script type="application/ld+json">` tags in `<head>`. The article frontmatter `json_ld` array becomes one or more `<script>` tags at render time.
|
|
4
|
+
|
|
5
|
+
## Recommended Schema by Page Type
|
|
6
|
+
|
|
7
|
+
| Page type | Required | Add when relevant |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| Pillar article | `Article` | `FAQPage`, `HowTo` |
|
|
10
|
+
| Sub-pillar article | `Article` | `FAQPage`, `HowTo` |
|
|
11
|
+
| Long-tail article | `Article` | `HowTo` |
|
|
12
|
+
| Landing page (homepage / about) | `Organization` | `WebSite`, `BreadcrumbList` |
|
|
13
|
+
| Landing page (pricing / product) | `Product` + `Offer` | `AggregateRating`, `Review` |
|
|
14
|
+
| Landing page (SaaS feature) | `SoftwareApplication` | `Offer` |
|
|
15
|
+
| FAQ page | `FAQPage` | — |
|
|
16
|
+
| Glossary entry | `Article` + `DefinedTerm` | — |
|
|
17
|
+
| Programmatic listing | `ItemList` | `Product`, `Organization`, `Place` per item |
|
|
18
|
+
| Local business / location | `LocalBusiness` (or specific subtype) | `OpeningHoursSpecification`, `GeoCoordinates` |
|
|
19
|
+
| Recipe (rare for SaaS — included for completeness) | `Recipe` | — |
|
|
20
|
+
|
|
21
|
+
## Article Schema Templates
|
|
22
|
+
|
|
23
|
+
### Article (default for blog content)
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"@context": "https://schema.org",
|
|
28
|
+
"@type": "Article",
|
|
29
|
+
"headline": "The Complete Technical SEO Guide for 2026",
|
|
30
|
+
"author": {
|
|
31
|
+
"@type": "Person",
|
|
32
|
+
"name": "Jane Doe",
|
|
33
|
+
"url": "https://example.com/authors/jane"
|
|
34
|
+
},
|
|
35
|
+
"datePublished": "2026-04-27",
|
|
36
|
+
"dateModified": "2026-04-27",
|
|
37
|
+
"image": "https://example.com/blog/technical-seo-guide/hero.png",
|
|
38
|
+
"publisher": {
|
|
39
|
+
"@type": "Organization",
|
|
40
|
+
"name": "Acme",
|
|
41
|
+
"logo": {
|
|
42
|
+
"@type": "ImageObject",
|
|
43
|
+
"url": "https://example.com/logo.png"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"mainEntityOfPage": "https://example.com/blog/technical-seo-guide"
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Required: `headline`, `author`, `datePublished`, `dateModified`, `image`, `publisher.logo`. Missing any of these and Google may reject the rich result.
|
|
51
|
+
|
|
52
|
+
### FAQPage (add to articles with FAQ sections)
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"@context": "https://schema.org",
|
|
57
|
+
"@type": "FAQPage",
|
|
58
|
+
"mainEntity": [
|
|
59
|
+
{
|
|
60
|
+
"@type": "Question",
|
|
61
|
+
"name": "What is technical SEO?",
|
|
62
|
+
"acceptedAnswer": {
|
|
63
|
+
"@type": "Answer",
|
|
64
|
+
"text": "Technical SEO is the practice of optimizing a website's infrastructure so search engines can crawl, render, and index it efficiently. It covers crawlability, indexation, site speed, schema markup, and AI search readiness."
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"@type": "Question",
|
|
69
|
+
"name": "How is technical SEO different from on-page SEO?",
|
|
70
|
+
"acceptedAnswer": {
|
|
71
|
+
"@type": "Answer",
|
|
72
|
+
"text": "Technical SEO covers infrastructure (crawl, render, index, speed). On-page SEO covers content and HTML structure (titles, meta, headings, keywords, internal links). Both matter; they target different layers."
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The `Answer.text` should be the actual paragraph from the article — Google flags mismatches.
|
|
80
|
+
|
|
81
|
+
### HowTo (add to step-by-step content)
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"@context": "https://schema.org",
|
|
86
|
+
"@type": "HowTo",
|
|
87
|
+
"name": "How to Submit a Sitemap to Google Search Console",
|
|
88
|
+
"description": "Step-by-step guide to submitting your sitemap.xml to Google Search Console.",
|
|
89
|
+
"totalTime": "PT5M",
|
|
90
|
+
"step": [
|
|
91
|
+
{
|
|
92
|
+
"@type": "HowToStep",
|
|
93
|
+
"name": "Open Google Search Console",
|
|
94
|
+
"text": "Sign in to Google Search Console at search.google.com/search-console.",
|
|
95
|
+
"url": "https://example.com/blog/submit-sitemap-gsc#step-1"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"@type": "HowToStep",
|
|
99
|
+
"name": "Navigate to Sitemaps",
|
|
100
|
+
"text": "In the left sidebar, click 'Sitemaps' under the 'Indexing' section.",
|
|
101
|
+
"url": "https://example.com/blog/submit-sitemap-gsc#step-2"
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Each step's `url` should be a fragment link to that step's heading on the article page. Use anchor IDs.
|
|
108
|
+
|
|
109
|
+
## Landing Page Schema Templates
|
|
110
|
+
|
|
111
|
+
### Organization (homepage)
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
{
|
|
115
|
+
"@context": "https://schema.org",
|
|
116
|
+
"@type": "Organization",
|
|
117
|
+
"name": "Acme",
|
|
118
|
+
"url": "https://acme.com",
|
|
119
|
+
"logo": "https://acme.com/logo.png",
|
|
120
|
+
"description": "Acme builds invoicing software for freelancers and small businesses.",
|
|
121
|
+
"foundingDate": "2024",
|
|
122
|
+
"sameAs": [
|
|
123
|
+
"https://twitter.com/acme",
|
|
124
|
+
"https://linkedin.com/company/acme",
|
|
125
|
+
"https://github.com/acme"
|
|
126
|
+
],
|
|
127
|
+
"contactPoint": {
|
|
128
|
+
"@type": "ContactPoint",
|
|
129
|
+
"contactType": "customer support",
|
|
130
|
+
"email": "support@acme.com"
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### WebSite (homepage — for sitelinks search box)
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"@context": "https://schema.org",
|
|
140
|
+
"@type": "WebSite",
|
|
141
|
+
"url": "https://acme.com",
|
|
142
|
+
"potentialAction": {
|
|
143
|
+
"@type": "SearchAction",
|
|
144
|
+
"target": "https://acme.com/search?q={search_term_string}",
|
|
145
|
+
"query-input": "required name=search_term_string"
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Product (pricing / product page)
|
|
151
|
+
|
|
152
|
+
```json
|
|
153
|
+
{
|
|
154
|
+
"@context": "https://schema.org",
|
|
155
|
+
"@type": "Product",
|
|
156
|
+
"name": "Acme Pro",
|
|
157
|
+
"description": "...",
|
|
158
|
+
"brand": { "@type": "Brand", "name": "Acme" },
|
|
159
|
+
"offers": [
|
|
160
|
+
{
|
|
161
|
+
"@type": "Offer",
|
|
162
|
+
"name": "Starter",
|
|
163
|
+
"price": "29",
|
|
164
|
+
"priceCurrency": "USD",
|
|
165
|
+
"availability": "https://schema.org/InStock",
|
|
166
|
+
"url": "https://acme.com/pricing#starter"
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"@type": "Offer",
|
|
170
|
+
"name": "Pro",
|
|
171
|
+
"price": "79",
|
|
172
|
+
"priceCurrency": "USD",
|
|
173
|
+
"availability": "https://schema.org/InStock",
|
|
174
|
+
"url": "https://acme.com/pricing#pro"
|
|
175
|
+
}
|
|
176
|
+
],
|
|
177
|
+
"aggregateRating": {
|
|
178
|
+
"@type": "AggregateRating",
|
|
179
|
+
"ratingValue": "4.8",
|
|
180
|
+
"reviewCount": "127"
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Only include `aggregateRating` if you have real review data — Google will manually penalize fake ratings.
|
|
186
|
+
|
|
187
|
+
### SoftwareApplication (SaaS feature page)
|
|
188
|
+
|
|
189
|
+
```json
|
|
190
|
+
{
|
|
191
|
+
"@context": "https://schema.org",
|
|
192
|
+
"@type": "SoftwareApplication",
|
|
193
|
+
"name": "Acme",
|
|
194
|
+
"applicationCategory": "BusinessApplication",
|
|
195
|
+
"operatingSystem": "Web",
|
|
196
|
+
"offers": {
|
|
197
|
+
"@type": "Offer",
|
|
198
|
+
"price": "29",
|
|
199
|
+
"priceCurrency": "USD"
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### LocalBusiness (location page)
|
|
205
|
+
|
|
206
|
+
```json
|
|
207
|
+
{
|
|
208
|
+
"@context": "https://schema.org",
|
|
209
|
+
"@type": "LocalBusiness",
|
|
210
|
+
"name": "Acme Plumbing — Chicago",
|
|
211
|
+
"image": "https://acme.com/locations/chicago.jpg",
|
|
212
|
+
"address": {
|
|
213
|
+
"@type": "PostalAddress",
|
|
214
|
+
"streetAddress": "123 Main St",
|
|
215
|
+
"addressLocality": "Chicago",
|
|
216
|
+
"addressRegion": "IL",
|
|
217
|
+
"postalCode": "60601",
|
|
218
|
+
"addressCountry": "US"
|
|
219
|
+
},
|
|
220
|
+
"geo": {
|
|
221
|
+
"@type": "GeoCoordinates",
|
|
222
|
+
"latitude": "41.8781",
|
|
223
|
+
"longitude": "-87.6298"
|
|
224
|
+
},
|
|
225
|
+
"telephone": "+1-555-0100",
|
|
226
|
+
"openingHoursSpecification": [
|
|
227
|
+
{
|
|
228
|
+
"@type": "OpeningHoursSpecification",
|
|
229
|
+
"dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
|
|
230
|
+
"opens": "08:00",
|
|
231
|
+
"closes": "18:00"
|
|
232
|
+
}
|
|
233
|
+
]
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Specific subtypes (`Restaurant`, `Plumber`, `Dentist`, etc.) win over generic `LocalBusiness` if your business fits one. See https://schema.org/LocalBusiness for the list.
|
|
238
|
+
|
|
239
|
+
## Cross-Cutting Schemas
|
|
240
|
+
|
|
241
|
+
### BreadcrumbList (every non-homepage page)
|
|
242
|
+
|
|
243
|
+
```json
|
|
244
|
+
{
|
|
245
|
+
"@context": "https://schema.org",
|
|
246
|
+
"@type": "BreadcrumbList",
|
|
247
|
+
"itemListElement": [
|
|
248
|
+
{ "@type": "ListItem", "position": 1, "name": "Home", "item": "https://acme.com" },
|
|
249
|
+
{ "@type": "ListItem", "position": 2, "name": "Blog", "item": "https://acme.com/blog" },
|
|
250
|
+
{ "@type": "ListItem", "position": 3, "name": "Technical SEO Guide", "item": "https://acme.com/blog/technical-seo-guide" }
|
|
251
|
+
]
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### DefinedTerm (glossary entries)
|
|
256
|
+
|
|
257
|
+
```json
|
|
258
|
+
{
|
|
259
|
+
"@context": "https://schema.org",
|
|
260
|
+
"@type": "DefinedTerm",
|
|
261
|
+
"name": "Crawl Budget",
|
|
262
|
+
"description": "The number of pages a search engine crawler will fetch from a site within a given timeframe.",
|
|
263
|
+
"inDefinedTermSet": {
|
|
264
|
+
"@type": "DefinedTermSet",
|
|
265
|
+
"name": "Acme SEO Glossary"
|
|
266
|
+
},
|
|
267
|
+
"url": "https://acme.com/glossary/crawl-budget"
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Validation
|
|
272
|
+
|
|
273
|
+
Always tell users to test before deploying:
|
|
274
|
+
- **Rich Results Test**: https://search.google.com/test/rich-results — checks if Google can parse the markup and which rich-result types it qualifies for
|
|
275
|
+
- **Schema Validator**: https://validator.schema.org — checks pure schema.org compliance
|
|
276
|
+
- Both tools accept either a URL or pasted code
|
|
277
|
+
|
|
278
|
+
Common validation errors:
|
|
279
|
+
- Missing required fields (`Article` requires `image`, `Product` requires `offers` with `price`)
|
|
280
|
+
- Invalid date formats (use ISO 8601: `2026-04-27` or `2026-04-27T10:00:00Z`)
|
|
281
|
+
- Wrong type for a field (`price` must be a string, not a number, in JSON-LD)
|
|
282
|
+
- Mismatched content (FAQ schema's `Answer.text` doesn't match the visible page text)
|
|
283
|
+
|
|
284
|
+
## When Multiple Schema Types Apply
|
|
285
|
+
|
|
286
|
+
A pillar article with FAQs and step-by-step instructions can have all three:
|
|
287
|
+
```yaml
|
|
288
|
+
json_ld:
|
|
289
|
+
- "@type": Article
|
|
290
|
+
...
|
|
291
|
+
- "@type": FAQPage
|
|
292
|
+
...
|
|
293
|
+
- "@type": HowTo
|
|
294
|
+
...
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Each renders as a separate `<script type="application/ld+json">` tag. Multiple types on one page is supported and recommended.
|