- Rewrite README.md with current architecture, features and stack - Update docs/API.md with all current endpoints (corporate, BI, client 360) - Update docs/ARCHITECTURE.md with cache, modular queries, services, ETL - Update docs/GUIA-USUARIO.md for all roles (admin, corporate, agente) - Add docs/INDEX.md documentation index - Add PROJETO.md comprehensive project reference - Add BI-CCC-Implementation-Guide.md - Include AI agent configs (.claude, .agents, .gemini, _bmad) - Add netbird VPN configuration - Add status report Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
15 KiB
SEO Validation Guide
For: WDS Agents performing Agentic Development Purpose: Verify SEO implementation against specification before presenting to user When: After a public page is built and previewable (browser or deployed)
Core Principle
Every public page must pass SEO validation before approval.
The agent verifies all measurable SEO criteria using browser tools (Puppeteer, MCP browser-tools, or manual inspection). SEO failures caught during development cost minutes to fix. SEO failures caught after deployment cost rankings and traffic.
When to Run SEO Validation
| Trigger | Action |
|---|---|
| Public page section complete (4c/4d) | Run SEO checks before presenting |
| Full page implementation complete | Run complete SEO audit |
| Pre-deployment review | Full validation against spec + project brief |
| Post-deployment verification | Validate live URL matches specification |
Reference Documents
Before running validation, gather:
- Page specification — SEO & Search section (keywords, URL, headings, meta)
- Project brief — SEO Strategy section (page-keyword map, structured data plan)
- SEO Strategy Guide —
../../data/agent-guides/saga/seo-strategy-guide.md
SEO Validation Checklist
Level 1: Critical (Must Pass)
These are the top errors found in real-world SEO audits. Failing any of these blocks approval.
1.1 Page Title Tag
Verify:
- Title tag exists and is not empty
- Length ≤ 60 characters (check each language)
- Contains primary keyword
- Contains brand name
- Is unique (different from other pages)
- Matches specification
Report:
"Page title is 'Bilservice på Öland | Källa Fordonservice' (51 chars)
— contains keyword 'bilservice', includes brand. ✓ Passes"
"Page title is 'Home' (4 chars)
— too short, no keyword, no brand. ✗ Fails"
1.2 Meta Description
Verify:
- Meta description tag exists and is not empty
- Length 150-160 characters
- Contains primary keyword
- Contains call-to-action
- Matches specification
Report:
"Meta description is 156 chars, contains 'bilservice Öland', ends with
'Ring oss idag!' ✓ Passes"
"Meta description is missing. ✗ Fails — 80% of audited sites miss this"
1.3 H1 Heading
Verify:
- Exactly ONE <h1> tag on the page
- Contains primary keyword (natural, not stuffed)
- Is visible (not hidden)
- Matches specification
Report:
"Found 1 <h1>: 'Bilservice och reparationer på Öland'
— contains keyword 'bilservice'. ✓ Passes"
"Found 0 <h1> tags. ✗ Fails — 75% of audited sites have H1 issues"
"Found 3 <h1> tags. ✗ Fails — only one H1 allowed per page"
1.4 Heading Hierarchy
Verify:
- Headings follow logical order (H1 → H2 → H3)
- No skipped levels (H1 → H3 without H2)
- No duplicate H1
Report:
"Heading hierarchy: H1 → H2 → H3 → H2 → H3 ✓ Logical flow"
"Heading hierarchy: H1 → H3 (skipped H2) ✗ Fix: Change H3 to H2"
1.5 Image Alt Text
Verify:
- ALL images have alt attributes
- Alt text is descriptive (not empty, not "image")
- Alt text exists in all required languages
- Decorative images have alt="" (empty, not missing)
Report:
"Found 8 images:
hero-image: alt='Källa Fordonservice verkstad...' ✓
service-ac: alt='AC-service på personbil' ✓
icon-phone: alt='' (decorative) ✓
team-photo: alt attribute MISSING ✗
Result: 7/8 images pass. 1 missing alt text."
Level 2: Important (Should Pass)
2.1 Open Graph / Social Sharing
Verify:
- og:title tag present
- og:description tag present
- og:image tag present (valid URL, image exists)
- og:type tag present
- twitter:card tag present
Report:
"Social sharing tags:
og:title: 'Bilservice Öland — Källa Fordonservice' ✓
og:description: present (148 chars) ✓
og:image: '/images/social/hem-social.jpg' ✓ (file exists)
og:type: 'website' ✓
twitter:card: 'summary_large_image' ✓
All social tags present."
"Missing: og:image ✗ — 70% of audited sites miss social tags"
2.2 Structured Data (Schema.org)
Verify:
- JSON-LD script tag exists
- Schema type matches project brief plan
- Required properties present (name, address, phone for LocalBusiness)
- JSON is valid (parseable)
Report:
"Structured data found:
@type: 'AutoRepair' ✓
name: 'Källa Fordonservice' ✓
address: complete ✓
telephone: '+46485-27070' ✓
openingHours: present ✓
JSON-LD validates. ✓ Passes"
"No structured data found. ✗ Fails — spec requires LocalBusiness schema"
2.3 Internal Links
Verify:
- Page has at least 2 internal links to other pages
- Links have descriptive anchor text (not "click here", "read more")
- No broken internal links (404s)
- No redirect chains (link → 301 → 301 → page)
Report:
"Internal links found: 5
'Läs mer om AC-service' → /ac-service ✓ Descriptive
'Ring oss' → tel:+46485-27070 ✓ CTA
'Klicka här' → /kontakt ✗ Non-descriptive anchor text
Result: 4/5 links pass."
2.4 URL / Slug
Verify:
- URL slug matches specification
- Slug is lowercase
- Uses hyphens (not underscores or spaces)
- No special characters (ä, ö, å)
- Keyword present in slug
Report:
"URL slug: /ac-service ✓ Matches spec, lowercase, keyword present"
"URL slug: /Sida?id=42 ✗ Not descriptive, no keyword"
2.5 Canonical URL
Verify:
- <link rel="canonical"> tag present
- Points to the correct URL (self-referencing)
- One canonical per page
Report:
"Canonical: <link rel='canonical' href='https://kallafordon.se/ac-service'> ✓"
"Canonical tag missing. ✗ Fails"
Level 3: Technical (Verify on Deployment)
These checks apply to the deployed/preview site, not the prototype.
3.1 Performance
Verify:
- Total page weight < 3MB
- Largest image < 400KB (hero) / < 200KB (other)
- Time to First Byte (TTFB) < 1.5s
- No uncompressed images (should be WebP or compressed JPEG)
Report:
"Page weight: 1.8MB ✓ (target < 3MB)
hero.jpg: 380KB ✓ (target < 400KB)
team.jpg: 1.2MB ✗ (target < 200KB — compress!)
icon.svg: 3KB ✓
TTFB: 0.8s ✓ (target < 1.5s)"
3.2 robots.txt
Verify:
- robots.txt exists (not 404)
- Allows crawling of public pages
- References sitemap
- Blocks admin/private pages
Report:
"robots.txt: exists ✓
Sitemap reference: present ✓
Public pages: allowed ✓
/wp-admin/: blocked ✓"
3.3 XML Sitemap
Verify:
- Sitemap exists at /sitemap.xml (or referenced location)
- Contains all public pages
- All URLs return 200 (no broken links)
- Includes all language versions (if multilingual)
Report:
"Sitemap: 32 URLs, all return 200 ✓
Includes /en/ versions ✓
Includes /de/ versions ✓"
3.4 hreflang Tags (Multilingual)
Verify:
- Each page declares all language alternates
- x-default points to primary language
- Tags are reciprocal (EN page links to SE, SE page links to EN)
Report:
"hreflang tags on /ac-service:
sv: /ac-service ✓
en: /en/ac-service ✓
de: /de/ac-service ✓
x-default: /ac-service ✓
All reciprocal. ✓ Passes"
3.5 Security Headers
Verify:
- HSTS present
- X-Content-Type-Options present
- X-Frame-Options present
- Referrer-Policy present
Report:
"Security headers: 2/6 present ✗
HSTS: missing
CSP: missing
X-Content-Type-Options: 'nosniff' ✓
X-Frame-Options: 'DENY' ✓
Referrer-Policy: missing
Permissions-Policy: missing
Note: 95% of audited sites fail security headers."
3.6 Favicon
Verify:
- Favicon exists (check <link rel="icon">)
- Multiple sizes available (16x16, 32x32, 180x180)
Report:
"Favicon: present ✓
16x16: ✓
32x32: ✓
apple-touch-icon (180x180): ✓"
Verification with Puppeteer
Automated SEO Check Script Pattern
// Navigate to page
await page.goto(pageUrl, { waitUntil: 'networkidle0' });
// 1. Title tag
const title = await page.title();
console.log(`Title: "${title}" (${title.length} chars)`);
// 2. Meta description
const metaDesc = await page.$eval(
'meta[name="description"]',
el => el.content
).catch(() => null);
console.log(`Meta desc: "${metaDesc}" (${metaDesc?.length || 0} chars)`);
// 3. H1 count and content
const h1s = await page.$$eval('h1', els => els.map(el => el.textContent.trim()));
console.log(`H1 tags: ${h1s.length} — "${h1s.join('", "')}"`);
// 4. Heading hierarchy
const headings = await page.$$eval('h1,h2,h3,h4,h5,h6', els =>
els.map(el => ({ tag: el.tagName, text: el.textContent.trim().substring(0, 50) }))
);
console.log('Heading hierarchy:', headings.map(h => h.tag).join(' → '));
// 5. Images without alt
const imagesNoAlt = await page.$$eval('img', els =>
els.filter(el => !el.hasAttribute('alt')).map(el => el.src)
);
console.log(`Images without alt: ${imagesNoAlt.length}`);
// 6. Open Graph tags
const ogTags = await page.$$eval('meta[property^="og:"]', els =>
els.map(el => ({ property: el.getAttribute('property'), content: el.content }))
);
console.log(`OG tags: ${ogTags.length}`, ogTags);
// 7. Structured data
const jsonLd = await page.$$eval('script[type="application/ld+json"]', els =>
els.map(el => JSON.parse(el.textContent))
).catch(() => []);
console.log(`Structured data: ${jsonLd.length} blocks`, jsonLd.map(j => j['@type']));
// 8. Canonical
const canonical = await page.$eval('link[rel="canonical"]', el => el.href).catch(() => null);
console.log(`Canonical: ${canonical || 'MISSING'}`);
// 9. Internal links
const links = await page.$$eval('a[href]', els =>
els.filter(el => el.href.startsWith(window.location.origin))
.map(el => ({ text: el.textContent.trim().substring(0, 40), href: el.href }))
);
console.log(`Internal links: ${links.length}`);
Narration Pattern
Group results by severity and narrate clearly:
## SEO Validation Report: {Page Name}
### Critical ✓/✗
Title tag: "Bilservice Öland | Källa Fordonservice" (51 chars) ✓
Meta description: "Komplett bilverkstad..." (156 chars) ✓
H1: 1 found — "Bilservice och reparationer på Öland" ✓
Heading hierarchy: H1 → H2 → H3 → H2 → H3 ✓
Image alt text: 7/8 images have alt ✗ (team-photo missing)
### Important ✓/✗
Open Graph: 5/5 tags present ✓
Structured data: AutoRepair schema valid ✓
Internal links: 5 found, 4/5 descriptive ✗ (1 "Klicka här")
URL slug: /ac-service ✓
Canonical: present, self-referencing ✓
### Technical (deployment only)
Page weight: 1.8MB ✓
Image sizes: 1 oversized (team.jpg 1.2MB) ✗
Security headers: 2/6 ✗
### Summary
Critical: 4/5 pass
Important: 4/5 pass
Technical: 1/3 pass
Action needed: Fix 1 missing alt text, 1 non-descriptive link,
1 oversized image, 4 security headers.
Integration with Phase 5 Flow
4a: Announce & Gather
4b: Create Story File
4c: Implement Section
↓
Agent runs Puppeteer verification (INLINE-TESTING-GUIDE)
Agent runs SEO validation (THIS GUIDE) — for public pages only
↓
All pass? ── No ──→ Agent fixes, re-verifies (loop)
│
Yes
↓
4d: Present for Testing
Story File Addition
Add SEO criteria to the story file's Agent-Verifiable section:
### SEO Criteria (Public Pages)
| # | Criterion | Expected | How to Verify |
|---|-----------|----------|---------------|
| S1 | Title tag | "Bilservice Öland \| Källa" ≤60 chars | Read document.title |
| S2 | Meta description | 150-160 chars, keyword present | Read meta[name=description] |
| S3 | H1 count | Exactly 1 | Count h1 elements |
| S4 | H1 keyword | Contains "bilservice" | Read h1 textContent |
| S5 | Heading hierarchy | H1→H2→H3, no skips | Scan all headings |
| S6 | Image alt coverage | 100% images have alt | Check img elements |
| S7 | OG tags | og:title, og:description, og:image | Check meta[property^=og:] |
| S8 | Internal links | ≥ 2, descriptive text | Count and check a[href] |
Integration with Acceptance Testing
When creating test scenarios (Phase 4 [H] Handover / Phase 5 [T] Acceptance Testing), include SEO as a test category:
seo_checks:
- id: 'SEO-001'
name: 'Page title correct'
verify:
- 'Title tag matches specification'
- 'Title ≤ 60 characters'
- 'Contains primary keyword'
- id: 'SEO-002'
name: 'Meta description correct'
verify:
- 'Meta description matches specification'
- 'Length 150-160 characters'
- 'Contains CTA'
- id: 'SEO-003'
name: 'Heading structure valid'
verify:
- 'Exactly one H1'
- 'No skipped heading levels'
- id: 'SEO-004'
name: 'Image alt text complete'
verify:
- 'All content images have alt text'
- 'Alt text in correct language'
- id: 'SEO-005'
name: 'Structured data valid'
verify:
- 'JSON-LD present and parseable'
- 'Schema type matches plan'
- 'Required properties present'
Anti-Patterns
- Never skip SEO validation on public pages — It's not optional
- Never approve a page with missing alt text — 85% of real sites fail this
- Never use "click here" or "read more" as link text — Describe the destination
- Never have more than one H1 — One per page, always
- Never deploy without meta description — 80% of sites miss this
- Never assume SEO "can be added later" — It's specification, not decoration
Common Fixes (From 44 Real-World Audits)
| Issue | Frequency | Fix Time | Fix |
|---|---|---|---|
| Missing alt text | 85% | 1 min/image | Add descriptive alt attribute |
| Missing meta description | 80% | 2 min/page | Add meta tag from spec |
| H1 missing or wrong | 75% | 1 min | Add/fix h1 tag |
| Missing OG tags | 70% | 3 min/page | Add og: meta tags from spec |
| Missing structured data | 65% | 5 min/page | Add JSON-LD script |
| Oversized images | 65% | 2 min/image | Compress + convert to WebP |
| Non-descriptive links | 30% | 1 min/link | Rewrite anchor text |
| Missing canonical | 40% | 1 min | Add link rel=canonical |
Total estimated fix time for a typical page: 15-20 minutes These are all preventable by validating during development.
Related Resources
- Inline Testing Guide:
INLINE-TESTING-GUIDE.md— General Puppeteer verification - SEO Strategy Guide:
../../data/agent-guides/saga/seo-strategy-guide.md— SEO reference - SEO Content Instructions:
../../4-ux-design/templates/instructions/seo-content.instructions.md— Spec-level SEO - Specification Quality:
../../data/agent-guides/freya/specification-quality.md— Quality checklist - Meta Content Guide:
../../data/agent-guides/freya/meta-content-guide.md— Meta tag details
SEO validation during development = zero SEO issues at launch. Validate as you build.