bmad-plus 0.4.1 → 0.4.3
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/CHANGELOG.md +38 -0
- package/README.md +4 -2
- package/package.json +5 -4
- package/readme-international/README.de.md +1 -1
- package/readme-international/README.es.md +1 -1
- package/readme-international/README.fr.md +1 -1
- package/src/bmad-plus/agents/pack-animated/animated-website-agent.md +325 -0
- package/src/bmad-plus/agents/pack-animated/templates/animated-website-workflow.md +55 -0
- package/src/bmad-plus/agents/pack-backup/backup-agent.md +71 -0
- package/src/bmad-plus/agents/pack-backup/templates/backup-workflow.md +51 -0
- package/src/bmad-plus/agents/pack-seo/SKILL.md +171 -0
- package/src/bmad-plus/agents/pack-seo/checklist.md +140 -0
- package/src/bmad-plus/agents/pack-seo/pagespeed-playbook.md +320 -0
- package/src/bmad-plus/agents/pack-seo/ref/audit-schema.json +187 -0
- package/src/bmad-plus/agents/pack-seo/ref/cwv-thresholds.md +87 -0
- package/src/bmad-plus/agents/pack-seo/ref/eeat-criteria.md +123 -0
- package/src/bmad-plus/agents/pack-seo/ref/geo-signals.md +167 -0
- package/src/bmad-plus/agents/pack-seo/ref/hreflang-rules.md +153 -0
- package/src/bmad-plus/agents/pack-seo/ref/quality-gates.md +133 -0
- package/src/bmad-plus/agents/pack-seo/ref/schema-catalog.md +91 -0
- package/src/bmad-plus/agents/pack-seo/ref/schema-templates.json +356 -0
- package/src/bmad-plus/agents/pack-seo/seo-chief.md +294 -0
- package/src/bmad-plus/agents/pack-seo/seo-judge.md +241 -0
- package/src/bmad-plus/agents/pack-seo/seo-scout.md +171 -0
- package/src/bmad-plus/agents/pack-seo/templates/seo-audit-workflow.md +241 -0
- package/src/bmad-plus/module.yaml +29 -0
- package/tools/cli/bmad-plus-cli.js +23 -0
- package/tools/cli/commands/doctor.js +175 -0
- package/tools/cli/commands/install.js +54 -16
- package/tools/cli/commands/uninstall.js +34 -8
- package/tools/cli/commands/update.js +172 -0
- package/tools/cli/i18n.js +425 -303
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: seo-engine
|
|
3
|
+
description: >
|
|
4
|
+
BMAD+ SEO Engine v2.1 — Complete SEO audit engine with 3 multi-role agents,
|
|
5
|
+
6-phase workflow, Python toolkit, Google API integration, and PageSpeed
|
|
6
|
+
perfection loop. Use when user says /seo or any SEO-related command.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# SEO Engine — Orchestrator
|
|
10
|
+
|
|
11
|
+
> By Laurent Rochetta | BMAD+ SEO Engine v2.1
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
This skill orchestrates 3 specialized agents through a structured workflow.
|
|
16
|
+
Load the full agent files only when activating that agent's phase.
|
|
17
|
+
|
|
18
|
+
## Command Router
|
|
19
|
+
|
|
20
|
+
When the user issues a `/seo` command, route as follows:
|
|
21
|
+
|
|
22
|
+
| Command | Agent(s) | Action |
|
|
23
|
+
|---------|----------|--------|
|
|
24
|
+
| `/seo full <url>` | Scout → Judge → Chief | Run all 6 phases |
|
|
25
|
+
| `/seo quick <url>` | Scout → Judge → Chief | Run phases 1–4 only |
|
|
26
|
+
| `/seo technical <url>` | Scout (Inspector) | Phase 2 technical only |
|
|
27
|
+
| `/seo content <url>` | Judge (Content Expert) | Phase 2 content only |
|
|
28
|
+
| `/seo geo <url>` | Judge (GEO Analyst) | Phase 3 only |
|
|
29
|
+
| `/seo schema <url>` | Judge (Schema Master) | Schema detection + validation |
|
|
30
|
+
| `/seo images <url>` | Judge (Content Expert) | Image audit subset |
|
|
31
|
+
| `/seo hreflang <url>` | Scout (Inspector) | Hreflang audit, ref: `ref/hreflang-rules.md` |
|
|
32
|
+
| `/seo pagespeed <url>` | Scout + Chief | PageSpeed perfection loop |
|
|
33
|
+
| `/seo plan <type>` | Chief (Strategist) | Strategic plan by industry |
|
|
34
|
+
| `/seo fix` | Chief (Strategist) | Auto-generate fixes from last audit |
|
|
35
|
+
| `/seo history` | Chief (Reporter) | Show score history |
|
|
36
|
+
| `/seo compare` | Chief (Reporter) | Compare with previous audit |
|
|
37
|
+
| `/seo competitor <url1> <url2>` | Scout + Judge + Chief | Benchmark two sites |
|
|
38
|
+
| `/seo api <url>` | (script) | Run Google APIs (PSI + CrUX + Rich Results) |
|
|
39
|
+
|
|
40
|
+
## Full Audit Orchestration (`/seo full`)
|
|
41
|
+
|
|
42
|
+
### Phase 1 — Reconnaissance
|
|
43
|
+
**Agent**: Scout (Crawler role)
|
|
44
|
+
**Load**: `agent/seo-scout.md`
|
|
45
|
+
|
|
46
|
+
1. Run `scripts/seo_fetch.py <url> --json` to fetch homepage
|
|
47
|
+
2. Run `scripts/seo_crawl.py <url> --depth 2 --max 25 --json` to discover structure
|
|
48
|
+
3. Detect business type from content analysis:
|
|
49
|
+
- **SaaS**: pricing page, features page, signup CTA
|
|
50
|
+
- **E-commerce**: product pages, cart, categories
|
|
51
|
+
- **Local business**: address, phone, map, opening hours
|
|
52
|
+
- **Publisher**: articles, blog, news, RSS feed
|
|
53
|
+
- **Agency**: services, portfolio, case studies
|
|
54
|
+
4. Check for `/robots.txt`, `/sitemap.xml`, `/llms.txt`
|
|
55
|
+
|
|
56
|
+
**Checkpoint**: Report discovery summary, ask "Continue with full audit?"
|
|
57
|
+
|
|
58
|
+
### Phase 2 — Deep Scan (PARALLEL)
|
|
59
|
+
**Agents**: Scout (Inspector) + Judge (Content Expert + Schema Master)
|
|
60
|
+
**Load**: `agent/seo-scout.md` + `agent/seo-judge.md`
|
|
61
|
+
|
|
62
|
+
Run Scout and Judge **simultaneously** on each discovered page:
|
|
63
|
+
|
|
64
|
+
**Scout checks** (9 categories — see `agent/seo-scout.md`):
|
|
65
|
+
- Crawlability, Indexability, Security, URL Structure, Mobile
|
|
66
|
+
- Core Web Vitals, Structured Data detection, JS Rendering, IndexNow
|
|
67
|
+
|
|
68
|
+
**Judge checks** (see `agent/seo-judge.md`):
|
|
69
|
+
- E-E-A-T evaluation (ref: `ref/eeat-criteria.md`)
|
|
70
|
+
- Content quality (ref: `ref/quality-gates.md`)
|
|
71
|
+
- Schema validation (ref: `ref/schema-catalog.md`)
|
|
72
|
+
- Image audit
|
|
73
|
+
- Internal/external link analysis
|
|
74
|
+
|
|
75
|
+
**Optional**: Run `scripts/seo_apis.py --all <url>` for live PageSpeed + CrUX data.
|
|
76
|
+
|
|
77
|
+
Use `scripts/seo_parse.py <file> --url <url> --json` on fetched HTML.
|
|
78
|
+
Use `scripts/seo_screenshot.py <url> --viewport mobile` for visual audit.
|
|
79
|
+
|
|
80
|
+
### Phase 3 — AI Readiness & GEO
|
|
81
|
+
**Agent**: Judge (GEO Analyst role)
|
|
82
|
+
**Reference**: `ref/geo-signals.md`
|
|
83
|
+
|
|
84
|
+
- Check AI crawler access (GPTBot, ClaudeBot, PerplexityBot)
|
|
85
|
+
- Verify llms.txt compliance
|
|
86
|
+
- Score passage citability (134–167 word blocks)
|
|
87
|
+
- Compute AI Readiness Score (0–100)
|
|
88
|
+
|
|
89
|
+
### Phase 4 — Scoring
|
|
90
|
+
**Agent**: Chief (Scorer role)
|
|
91
|
+
**Load**: `agent/seo-chief.md`
|
|
92
|
+
|
|
93
|
+
Compute SEO Health Score (0–100):
|
|
94
|
+
|
|
95
|
+
| Category | Weight |
|
|
96
|
+
|----------|--------|
|
|
97
|
+
| Technical SEO | 20% |
|
|
98
|
+
| Content & E-E-A-T | 22% |
|
|
99
|
+
| On-Page SEO | 18% |
|
|
100
|
+
| Schema | 10% |
|
|
101
|
+
| Performance (CWV) | 12% |
|
|
102
|
+
| AI Readiness (GEO) | 12% |
|
|
103
|
+
| Images | 6% |
|
|
104
|
+
|
|
105
|
+
### Phase 5 — Action Plan
|
|
106
|
+
**Agent**: Chief (Strategist role)
|
|
107
|
+
|
|
108
|
+
1. Classify issues: 🔴 Critical → 🟠 High → 🟡 Medium → 🟢 Low
|
|
109
|
+
2. Identify quick wins (highest impact/effort ratio)
|
|
110
|
+
3. Generate 30/60/90-day roadmap
|
|
111
|
+
4. Auto-generate code fixes (meta tags, schema JSON-LD, robots.txt, llms.txt)
|
|
112
|
+
|
|
113
|
+
**Checkpoint**: "Here's the plan. Apply fixes automatically?"
|
|
114
|
+
|
|
115
|
+
### Phase 5b — PageSpeed Perfection Loop
|
|
116
|
+
**Agents**: Scout + Chief
|
|
117
|
+
**Reference**: `pagespeed-playbook.md` + `checklist.md`
|
|
118
|
+
|
|
119
|
+
Use `scripts/seo_apis.py --pagespeed <url>` for live scores.
|
|
120
|
+
Loop: fix one issue → re-test → verify improvement → next issue.
|
|
121
|
+
Target: 100% on all 4 categories (Performance, Accessibility, Best Practices, SEO).
|
|
122
|
+
|
|
123
|
+
### Phase 6 — Monitoring (optional)
|
|
124
|
+
**Agent**: Scout (Crawler role)
|
|
125
|
+
|
|
126
|
+
Save results to `.bmad-seo/history/<domain>-<date>.json`.
|
|
127
|
+
On re-audit: compare with previous, show deltas.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Python Toolkit
|
|
132
|
+
|
|
133
|
+
| Script | Usage | Dependencies |
|
|
134
|
+
|--------|-------|-------------|
|
|
135
|
+
| `seo_fetch.py` | `python scripts/seo_fetch.py <url> [--ua googlebot] [--json]` | requests |
|
|
136
|
+
| `seo_parse.py` | `python scripts/seo_parse.py <file> --url <url> --json` | beautifulsoup4, lxml |
|
|
137
|
+
| `seo_crawl.py` | `python scripts/seo_crawl.py <url> --depth 2 --max 25 --json` | requests |
|
|
138
|
+
| `seo_screenshot.py` | `python scripts/seo_screenshot.py <url> --viewport mobile` | playwright |
|
|
139
|
+
| `seo_apis.py` | `python scripts/seo_apis.py --pagespeed <url>` | requests |
|
|
140
|
+
|
|
141
|
+
**Install dependencies**: `pip install -r requirements.txt`
|
|
142
|
+
|
|
143
|
+
**Environment**: Set `GOOGLE_API_KEY` for Google API access (free, no OAuth).
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Reference Files (lazy-load)
|
|
148
|
+
|
|
149
|
+
Only load these when the relevant agent needs them:
|
|
150
|
+
- `ref/cwv-thresholds.md` — Core Web Vitals 2026
|
|
151
|
+
- `ref/schema-catalog.md` — Schema.org v29.4 types
|
|
152
|
+
- `ref/eeat-criteria.md` — E-E-A-T scoring grid
|
|
153
|
+
- `ref/geo-signals.md` — AI search signals
|
|
154
|
+
- `ref/quality-gates.md` — Content thresholds
|
|
155
|
+
- `ref/schema-templates.json` — 14 JSON-LD templates
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Industry-Specific Plans (`/seo plan <type>`)
|
|
160
|
+
|
|
161
|
+
| Type | Focus |
|
|
162
|
+
|------|-------|
|
|
163
|
+
| `saas` | Pricing pages, feature comparison, trial CTAs, documentation SEO |
|
|
164
|
+
| `ecommerce` | Product schema, category pages, faceted navigation, review markup |
|
|
165
|
+
| `local` | LocalBusiness schema, Google Business Profile, location pages, NAP consistency |
|
|
166
|
+
| `publisher` | Article schema, author E-E-A-T, news sitemap, pagination |
|
|
167
|
+
| `agency` | Service schema, portfolio, case studies, city-specific landing pages |
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
*BMAD+ SEO Engine v2.1 — By Laurent Rochetta*
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# SEO/GEO 360° Audit Checklist
|
|
2
|
+
|
|
3
|
+
## Usage
|
|
4
|
+
Use this checklist as a template for any website audit. Copy it to your project's output folder and check items as you go.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. Technical SEO
|
|
9
|
+
- [ ] `robots.txt` exists and is correctly configured
|
|
10
|
+
- [ ] `sitemap.xml` exists with all pages, `lastmod`, `changefreq`, `priority`
|
|
11
|
+
- [ ] Sitemap includes `xhtml:link` hreflang alternates (if multilingual)
|
|
12
|
+
- [ ] Canonical URLs present on all pages
|
|
13
|
+
- [ ] Hreflang tags for each language variant
|
|
14
|
+
- [ ] Meta robots: `index, follow, max-image-preview:large, max-snippet:-1`
|
|
15
|
+
- [ ] HTTPS enforced
|
|
16
|
+
- [ ] No render-blocking resources (fonts preloaded, CSS inlined or local)
|
|
17
|
+
- [ ] Mobile viewport meta tag present
|
|
18
|
+
- [ ] Favicon present (SVG preferred, fallback PNG)
|
|
19
|
+
- [ ] 404 error page exists
|
|
20
|
+
- [ ] Page load time < 3 seconds
|
|
21
|
+
|
|
22
|
+
## 2. On-Page SEO
|
|
23
|
+
- [ ] `<title>` tag: 50-60 chars, contains primary keyword, unique per page
|
|
24
|
+
- [ ] `<meta description>`: 150-160 chars, contains CTA, unique per page
|
|
25
|
+
- [ ] `<meta keywords>`: relevant long-tail keywords
|
|
26
|
+
- [ ] `<meta author>`: present
|
|
27
|
+
- [ ] Single `<h1>` per page, descriptive, keyword-rich
|
|
28
|
+
- [ ] Logical H2-H6 hierarchy
|
|
29
|
+
- [ ] Images: `alt` tags, `width`/`height`, `loading="lazy"`, WebP format
|
|
30
|
+
- [ ] Internal linking between pages
|
|
31
|
+
- [ ] External links: `rel="noopener"` on `target="_blank"`
|
|
32
|
+
- [ ] Content length adequate (300+ words for landing pages)
|
|
33
|
+
|
|
34
|
+
## 3. Schema.org / Structured Data
|
|
35
|
+
- [ ] Primary schema type (Organization/LocalBusiness/ProfessionalService)
|
|
36
|
+
- [ ] Service schema for each service offered
|
|
37
|
+
- [ ] FAQPage schema with real Q&As (minimum 5 questions)
|
|
38
|
+
- [ ] WebPage schema (datePublished, dateModified, inLanguage)
|
|
39
|
+
- [ ] BreadcrumbList schema
|
|
40
|
+
- [ ] Person schema for founder/team (if applicable)
|
|
41
|
+
- [ ] Review/AggregateRating schema (if applicable)
|
|
42
|
+
- [ ] Validated with Google Rich Results Test
|
|
43
|
+
|
|
44
|
+
## 4. GEO (Generative Engine Optimization)
|
|
45
|
+
- [ ] `llms.txt` file at site root with structured business info
|
|
46
|
+
- [ ] Content uses clear headings with question-based format
|
|
47
|
+
- [ ] FAQ section with expandable answers
|
|
48
|
+
- [ ] Concise, factual content per section (answers visible without interaction)
|
|
49
|
+
- [ ] AI crawlers allowed in `robots.txt`:
|
|
50
|
+
- [ ] GPTBot
|
|
51
|
+
- [ ] ChatGPT-User
|
|
52
|
+
- [ ] PerplexityBot
|
|
53
|
+
- [ ] ClaudeBot
|
|
54
|
+
- [ ] Google-Extended
|
|
55
|
+
- [ ] Content freshness: `dateModified` in schema, visible update dates
|
|
56
|
+
- [ ] E-E-A-T signals: author credentials, "About" info, LinkedIn links
|
|
57
|
+
- [ ] Tested queries in ChatGPT, Perplexity, and Gemini
|
|
58
|
+
|
|
59
|
+
## 5. Local SEO
|
|
60
|
+
- [ ] Geo meta tags: `geo.region`, `geo.placename`, `geo.position`, `ICBM`
|
|
61
|
+
- [ ] NAP (Name, Address, Phone/Email) in footer
|
|
62
|
+
- [ ] `<address>` HTML element used
|
|
63
|
+
- [ ] Schema includes `geo` (GeoCoordinates), `areaServed`, `address`
|
|
64
|
+
- [ ] Google Business Profile created and optimized
|
|
65
|
+
- [ ] Local keywords in title, description, and H1
|
|
66
|
+
- [ ] Citations on directories (Pages Jaunes, Google Maps, etc.)
|
|
67
|
+
|
|
68
|
+
## 6. Accessibility
|
|
69
|
+
- [ ] Skip navigation link
|
|
70
|
+
- [ ] `aria-hidden="true"` on decorative SVGs and elements
|
|
71
|
+
- [ ] `aria-label` on `<section>` and `<nav>` elements
|
|
72
|
+
- [ ] `aria-pressed` on toggle buttons
|
|
73
|
+
- [ ] Color contrast WCAG AA (4.5:1 minimum)
|
|
74
|
+
- [ ] Keyboard-navigable form elements
|
|
75
|
+
- [ ] `<html lang>` attribute set correctly (dynamic for multilingual)
|
|
76
|
+
- [ ] `prefers-reduced-motion` media query support
|
|
77
|
+
|
|
78
|
+
## 7. Social & Sharing
|
|
79
|
+
- [ ] Open Graph: `og:title`, `og:description`, `og:image`, `og:url`, `og:type`
|
|
80
|
+
- [ ] `og:site_name` present
|
|
81
|
+
- [ ] `og:locale` with `og:locale:alternate` for other languages
|
|
82
|
+
- [ ] `og:image` dimensions 1200×630px
|
|
83
|
+
- [ ] Twitter Card: `twitter:card` (summary_large_image)
|
|
84
|
+
- [ ] Social preview tested with sharing debugger tools
|
|
85
|
+
|
|
86
|
+
## 8. Multilingual SEO (if applicable)
|
|
87
|
+
- [ ] `<html lang>` dynamic based on selected language
|
|
88
|
+
- [ ] Bilingual `<title>` and `<meta description>` (via server-side logic)
|
|
89
|
+
- [ ] Hreflang tags for each page × each language
|
|
90
|
+
- [ ] `x-default` hreflang set
|
|
91
|
+
- [ ] Schema.org `description` in correct language
|
|
92
|
+
- [ ] OG tags in correct language (`og:locale`)
|
|
93
|
+
- [ ] Sitemap includes hreflang alternates per URL
|
|
94
|
+
- [ ] Language preference persisted (localStorage/cookie)
|
|
95
|
+
- [ ] Auto-detection of browser language (`navigator.language`)
|
|
96
|
+
- [ ] Legal pages (Privacy, Terms) translated
|
|
97
|
+
|
|
98
|
+
## 9. Content & Keywords
|
|
99
|
+
- [ ] Primary keyword identified per page
|
|
100
|
+
- [ ] Long-tail keyword variants in content
|
|
101
|
+
- [ ] Location-based keywords (city, region, country)
|
|
102
|
+
- [ ] Service-specific keywords
|
|
103
|
+
- [ ] Competitor keyword gap analysis
|
|
104
|
+
- [ ] Content matches search intent (informational, transactional, navigational)
|
|
105
|
+
|
|
106
|
+
## 10. PageSpeed 100% — Performance Perfection Loop
|
|
107
|
+
> Reference: See `pagespeed-playbook.md` for complete technique catalog with code examples.
|
|
108
|
+
|
|
109
|
+
### Performance (Impact-Ordered)
|
|
110
|
+
- [ ] Self-hosted fonts (woff2) — no Google CDN
|
|
111
|
+
- [ ] Font preload with `fetchpriority="high"` for critical weight
|
|
112
|
+
- [ ] `font-display: swap` on all `@font-face` rules
|
|
113
|
+
- [ ] ALL CSS inlined in production HTML (zero render-blocking)
|
|
114
|
+
- [ ] `.htaccess` cache headers: 1yr for static assets, 1hr for HTML
|
|
115
|
+
- [ ] Gzip/Brotli compression enabled (mod_deflate)
|
|
116
|
+
- [ ] ALL `<img>` tags have explicit `width` and `height` attributes
|
|
117
|
+
- [ ] Below-fold images have `loading="lazy"`
|
|
118
|
+
- [ ] LCP image has `fetchpriority="high"`
|
|
119
|
+
- [ ] Third-party scripts use `async defer`
|
|
120
|
+
- [ ] CLS = 0 (no layout shifts during load)
|
|
121
|
+
|
|
122
|
+
### Accessibility (WCAG AA)
|
|
123
|
+
- [ ] ALL text/background combos ≥ 4.5:1 contrast ratio
|
|
124
|
+
- [ ] White text on orange backgrounds replaced with dark text (#1a0800)
|
|
125
|
+
- [ ] Dim/muted text on dark backgrounds brightened to ≥ #a0b0b8
|
|
126
|
+
- [ ] Badge text with transparency backgrounds ≥ #d4dde3
|
|
127
|
+
- [ ] Checked: CTA buttons, active nav states, badges, footer, form labels, tags
|
|
128
|
+
|
|
129
|
+
### Best Practices
|
|
130
|
+
- [ ] No console errors from own code
|
|
131
|
+
- [ ] Third-party console errors documented as known limitations
|
|
132
|
+
- [ ] All external links have `rel="noopener noreferrer"`
|
|
133
|
+
- [ ] All resources loaded over HTTPS
|
|
134
|
+
|
|
135
|
+
### Anti-Patterns (NEVER DO)
|
|
136
|
+
- [ ] ❌ Never use `media="print" onload` for CSS (causes CLS 0.936)
|
|
137
|
+
- [ ] ❌ Never load fonts from external CDN without local fallback
|
|
138
|
+
- [ ] ❌ Never leave `<img>` tags without width/height dimensions
|
|
139
|
+
- [ ] ❌ Never fix contrast issues one at a time (batch ALL at once)
|
|
140
|
+
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
# 🎯 PageSpeed Perfection Playbook — Battle-Tested Guide
|
|
2
|
+
|
|
3
|
+
> **Version 1.0.0** — Based on 6+ real-world iterations achieving 99-100% on oveanet.ch, montpellier.ai, and oveanet.fr
|
|
4
|
+
> By Laurent ROCHETTA × AI
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 📐 Architecture: The 4-Phase Protocol
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
Phase 1: DIAGNOSTIC → Extract scores, identify all failing audits
|
|
12
|
+
Phase 2: PERFORMANCE → Self-host fonts → Inline CSS → Cache → CLS
|
|
13
|
+
Phase 3: ACCESSIBILITY → WCAG AA contrast fixes (systematic)
|
|
14
|
+
Phase 4: BEST PRACTICES & SEO → Console errors, security, meta tags
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Each phase runs sequentially. **Backup before each phase.** Re-test after each phase.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## ⚠️ Anti-Patterns — Things That BREAK Scores
|
|
22
|
+
|
|
23
|
+
> [!CAUTION]
|
|
24
|
+
> These are real mistakes made during optimization that caused score REGRESSIONS.
|
|
25
|
+
|
|
26
|
+
### 🔴 NEVER: Async CSS Loading
|
|
27
|
+
```html
|
|
28
|
+
<!-- ❌ DISASTER — causes CLS 0.936 on mobile -->
|
|
29
|
+
<link rel="stylesheet" href="styles.css" media="print" onload="this.media='all'"/>
|
|
30
|
+
<noscript><link rel="stylesheet" href="styles.css"/></noscript>
|
|
31
|
+
```
|
|
32
|
+
**What happens:** The page renders with no styles, then FLASHES when CSS loads. PageSpeed measures this as a massive Cumulative Layout Shift (0.936 — nearly maximum). Performance dropped from 86 to 75.
|
|
33
|
+
|
|
34
|
+
**Fix:** Either load CSS synchronously or inline it entirely.
|
|
35
|
+
|
|
36
|
+
### 🔴 NEVER: External Font CDN Without Fallback
|
|
37
|
+
```html
|
|
38
|
+
<!-- ❌ Adds 300-800ms to LCP on mobile 4G -->
|
|
39
|
+
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;600;700" rel="stylesheet"/>
|
|
40
|
+
```
|
|
41
|
+
**What happens:** DNS lookup + TLS handshake to `fonts.googleapis.com`, then another to `fonts.gstatic.com`. On 4G simulation, this adds 500ms+ to LCP.
|
|
42
|
+
|
|
43
|
+
### 🔴 NEVER: Images Without Dimensions
|
|
44
|
+
```html
|
|
45
|
+
<!-- ❌ Causes CLS because browser doesn't know the space to reserve -->
|
|
46
|
+
<img src="logo.svg" alt="Logo"/>
|
|
47
|
+
```
|
|
48
|
+
**Fix:** Always add `width` and `height`:
|
|
49
|
+
```html
|
|
50
|
+
<img src="logo.svg" alt="Logo" width="100" height="20"/>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 🏆 Technique Catalog — Ordered by Impact
|
|
56
|
+
|
|
57
|
+
### [P1] Self-Host Fonts (Biggest LCP Win)
|
|
58
|
+
|
|
59
|
+
**Impact:** LCP -30-50% on mobile, eliminates 2 external requests
|
|
60
|
+
|
|
61
|
+
**Step 1:** Download fonts from Google Fonts API or copy from existing projects:
|
|
62
|
+
```
|
|
63
|
+
fonts/
|
|
64
|
+
├── space-grotesk-400-latin.woff2
|
|
65
|
+
├── space-grotesk-600-latin.woff2
|
|
66
|
+
└── space-grotesk-700-latin.woff2
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Step 2:** Update CSS `@font-face` declarations:
|
|
70
|
+
```css
|
|
71
|
+
/* ✅ Self-hosted — zero external requests */
|
|
72
|
+
@font-face {
|
|
73
|
+
font-family: 'Space Grotesk';
|
|
74
|
+
font-style: normal;
|
|
75
|
+
font-weight: 400;
|
|
76
|
+
font-display: swap;
|
|
77
|
+
src: url('fonts/space-grotesk-400-latin.woff2') format('woff2');
|
|
78
|
+
}
|
|
79
|
+
@font-face {
|
|
80
|
+
font-family: 'Space Grotesk';
|
|
81
|
+
font-style: normal;
|
|
82
|
+
font-weight: 600;
|
|
83
|
+
font-display: swap;
|
|
84
|
+
src: url('fonts/space-grotesk-600-latin.woff2') format('woff2');
|
|
85
|
+
}
|
|
86
|
+
@font-face {
|
|
87
|
+
font-family: 'Space Grotesk';
|
|
88
|
+
font-style: normal;
|
|
89
|
+
font-weight: 700;
|
|
90
|
+
font-display: swap;
|
|
91
|
+
src: url('fonts/space-grotesk-700-latin.woff2') format('woff2');
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Step 3:** Preload critical fonts in HTML `<head>`:
|
|
96
|
+
```html
|
|
97
|
+
<link rel="preload" href="fonts/space-grotesk-400-latin.woff2"
|
|
98
|
+
as="font" type="font/woff2" crossorigin fetchpriority="high"/>
|
|
99
|
+
<link rel="preload" href="fonts/space-grotesk-600-latin.woff2"
|
|
100
|
+
as="font" type="font/woff2" crossorigin/>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Step 4:** Remove ALL Google Fonts CDN references:
|
|
104
|
+
```diff
|
|
105
|
+
-<link rel="preconnect" href="https://fonts.googleapis.com"/>
|
|
106
|
+
-<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
|
|
107
|
+
-<link href="https://fonts.googleapis.com/css2?..." rel="stylesheet"/>
|
|
108
|
+
+<!-- Fonts are self-hosted, no external CDN needed -->
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### How to get woff2 files from Google Fonts:
|
|
112
|
+
```bash
|
|
113
|
+
# Method 1: Use google-webfonts-helper
|
|
114
|
+
# https://gwfh.mranftl.com/fonts
|
|
115
|
+
|
|
116
|
+
# Method 2: Direct download with curl (set woff2 user-agent)
|
|
117
|
+
curl -H "User-Agent: Mozilla/5.0" \
|
|
118
|
+
"https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;600;700&display=swap" \
|
|
119
|
+
| grep -oP 'url\(\K[^)]+' | while read url; do
|
|
120
|
+
wget "$url" -P fonts/
|
|
121
|
+
done
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
### [P2] Inline ALL CSS (Zero Render-Blocking)
|
|
127
|
+
|
|
128
|
+
**Impact:** Eliminates render-blocking CSS request, reduces FCP by 100-300ms
|
|
129
|
+
|
|
130
|
+
**For PHP sites** (like montpellier.ai):
|
|
131
|
+
```php
|
|
132
|
+
<!-- ✅ CSS inlined at build time — zero render-blocking -->
|
|
133
|
+
<style><?php echo file_get_contents(__DIR__ . '/styles.css'); ?></style>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**For static HTML sites** (build script):
|
|
137
|
+
|
|
138
|
+
PowerShell build script:
|
|
139
|
+
```powershell
|
|
140
|
+
# build-dist.ps1 — Inline CSS into HTML for production
|
|
141
|
+
$css = Get-Content "styles.css" -Raw
|
|
142
|
+
$html = Get-Content "index.html" -Raw
|
|
143
|
+
|
|
144
|
+
$cssInline = " <style>`n$css`n </style>"
|
|
145
|
+
|
|
146
|
+
# Replace the critical CSS + external link with full inline
|
|
147
|
+
$pattern = ' <!-- Critical CSS.*?</style>\s*\r?\n.*?<link rel="stylesheet" href="styles.css"/>'
|
|
148
|
+
$html = $html -replace $pattern, $cssInline
|
|
149
|
+
|
|
150
|
+
$html | Set-Content "dist/index.html" -Encoding UTF8
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Bash build script:
|
|
154
|
+
```bash
|
|
155
|
+
#!/bin/bash
|
|
156
|
+
# build-dist.sh — Inline CSS into HTML for production
|
|
157
|
+
CSS=$(cat styles.css)
|
|
158
|
+
sed -e '/<!-- Critical CSS/,/<link rel="stylesheet"/c\ <style>'"$CSS"'</style>' \
|
|
159
|
+
index.html > dist/index.html
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
> [!IMPORTANT]
|
|
163
|
+
> Keep source files separate (index.html + styles.css) for development.
|
|
164
|
+
> Only the dist/ version gets the inlined CSS for production.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
### [P3] Cache Headers (.htaccess Template)
|
|
169
|
+
|
|
170
|
+
**Impact:** Fixes "Use efficient cache policy" audit
|
|
171
|
+
|
|
172
|
+
```apache
|
|
173
|
+
# === PageSpeed Cache & Compression — Ready to Deploy ===
|
|
174
|
+
|
|
175
|
+
<IfModule mod_expires.c>
|
|
176
|
+
ExpiresActive On
|
|
177
|
+
ExpiresDefault "access plus 1 month"
|
|
178
|
+
ExpiresByType text/html "access plus 1 hour"
|
|
179
|
+
ExpiresByType text/css "access plus 1 year"
|
|
180
|
+
ExpiresByType application/javascript "access plus 1 year"
|
|
181
|
+
ExpiresByType image/svg+xml "access plus 1 year"
|
|
182
|
+
ExpiresByType image/png "access plus 1 year"
|
|
183
|
+
ExpiresByType image/webp "access plus 1 year"
|
|
184
|
+
ExpiresByType font/woff2 "access plus 1 year"
|
|
185
|
+
ExpiresByType application/font-woff2 "access plus 1 year"
|
|
186
|
+
</IfModule>
|
|
187
|
+
|
|
188
|
+
<IfModule mod_headers.c>
|
|
189
|
+
<FilesMatch "\.(css|js|svg|png|webp|woff2|ico)$">
|
|
190
|
+
Header set Cache-Control "public, max-age=31536000, immutable"
|
|
191
|
+
</FilesMatch>
|
|
192
|
+
<FilesMatch "\.(html|php)$">
|
|
193
|
+
Header set Cache-Control "public, max-age=3600, must-revalidate"
|
|
194
|
+
</FilesMatch>
|
|
195
|
+
<FilesMatch "\.(txt|xml)$">
|
|
196
|
+
Header set Cache-Control "public, max-age=86400"
|
|
197
|
+
</FilesMatch>
|
|
198
|
+
</IfModule>
|
|
199
|
+
|
|
200
|
+
# Gzip Compression
|
|
201
|
+
<IfModule mod_deflate.c>
|
|
202
|
+
AddOutputFilterByType DEFLATE text/html text/css application/javascript
|
|
203
|
+
AddOutputFilterByType DEFLATE text/javascript application/json text/xml
|
|
204
|
+
AddOutputFilterByType DEFLATE image/svg+xml text/plain
|
|
205
|
+
</IfModule>
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
### [P4] CLS Prevention Checklist
|
|
211
|
+
|
|
212
|
+
| Element | Required Fix |
|
|
213
|
+
|---------|-------------|
|
|
214
|
+
| `<img>` tags | Add `width` and `height` attributes |
|
|
215
|
+
| CSS-loaded fonts | Use `font-display: swap` |
|
|
216
|
+
| CSS loading | NEVER use `media="print" onload` trick |
|
|
217
|
+
| Dynamic content | Reserve space with min-height |
|
|
218
|
+
| Embeds/iframes | Set explicit dimensions |
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## 🎨 Accessibility — Contrast Ratio Reference
|
|
223
|
+
|
|
224
|
+
### The Rule
|
|
225
|
+
WCAG AA requires **4.5:1** contrast ratio for normal text, **3:1** for large text (18px+ or 14px+ bold).
|
|
226
|
+
|
|
227
|
+
### Common Color Combos on Dark Backgrounds (#0B0C15)
|
|
228
|
+
|
|
229
|
+
| Text Color | Hex | Ratio vs #0B0C15 | Pass? |
|
|
230
|
+
|-----------|-----|:-----------------:|:-----:|
|
|
231
|
+
| Pure white | `#ffffff` | 19.2:1 | ✅ |
|
|
232
|
+
| Light gray | `#d4dde3` | 12.5:1 | ✅ |
|
|
233
|
+
| Medium gray | `#a0b0b8` | 6.5:1 | ✅ |
|
|
234
|
+
| Muted gray | `#b0bec5` | 8.0:1 | ✅ |
|
|
235
|
+
| Dim gray | `#6b7b8a` | 3.7:1 | ❌ |
|
|
236
|
+
| Very dim | `#4a5568` | 2.3:1 | ❌ |
|
|
237
|
+
|
|
238
|
+
### Common Color Combos on Orange (#E8632B)
|
|
239
|
+
|
|
240
|
+
| Text Color | Hex | Ratio vs #E8632B | Pass? |
|
|
241
|
+
|-----------|-----|:-----------------:|:-----:|
|
|
242
|
+
| White | `#ffffff` | 3.3:1 | ❌ FAIL |
|
|
243
|
+
| Light cream | `#fff5e6` | 3.1:1 | ❌ FAIL |
|
|
244
|
+
| Dark brown | `#1a0800` | 8.5:1 | ✅ |
|
|
245
|
+
| Black | `#000000` | 4.0:1 | ✅ (large) |
|
|
246
|
+
|
|
247
|
+
> [!WARNING]
|
|
248
|
+
> **White text on orange buttons is a classic fail.** Ratio is only 3.3:1.
|
|
249
|
+
> Use dark text (`#1a0800`) instead for buttons, badges, and active states with orange backgrounds.
|
|
250
|
+
|
|
251
|
+
### Elements Commonly Flagged by PageSpeed
|
|
252
|
+
1. **CTA buttons** (`.btn-primary`) — white on primary color
|
|
253
|
+
2. **Active language buttons** (`.lang-btn.active`) — white on primary
|
|
254
|
+
3. **Pricing badges** ("Most Popular") — white on primary
|
|
255
|
+
4. **Footer text** — dim on very dark background
|
|
256
|
+
5. **Form labels** — muted on dark
|
|
257
|
+
6. **Badge text** (partner badges, sponsor badges) — muted on dark with transparency
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## 📊 Score Tracking Template
|
|
262
|
+
|
|
263
|
+
```markdown
|
|
264
|
+
| Iteration | Perf (M) | A11y (M) | Perf (D) | A11y (D) | BP | SEO | Changes |
|
|
265
|
+
|:---------:|:--------:|:--------:|:--------:|:--------:|:---:|:---:|---------|
|
|
266
|
+
| Baseline | ? | ? | ? | ? | ? | ? | Initial |
|
|
267
|
+
| #1 | | | | | | | |
|
|
268
|
+
| #2 | | | | | | | |
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## 🔧 PageSpeed JavaScript Extraction Snippets
|
|
274
|
+
|
|
275
|
+
Use these in the browser to extract structured data from PageSpeed results:
|
|
276
|
+
|
|
277
|
+
### Extract All Scores
|
|
278
|
+
```javascript
|
|
279
|
+
JSON.stringify({
|
|
280
|
+
gauges: Array.from(document.querySelectorAll('.lh-gauge__percentage'))
|
|
281
|
+
.map(g => g.textContent.trim()),
|
|
282
|
+
labels: Array.from(document.querySelectorAll('.lh-gauge__label'))
|
|
283
|
+
.map(l => l.textContent.trim())
|
|
284
|
+
});
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Extract Core Web Vitals
|
|
288
|
+
```javascript
|
|
289
|
+
JSON.stringify({
|
|
290
|
+
metrics: Array.from(document.querySelectorAll('.lh-metric'))
|
|
291
|
+
.map(m => ({
|
|
292
|
+
name: m.querySelector('.lh-metric__title')?.textContent?.trim(),
|
|
293
|
+
value: m.querySelector('.lh-metric__value')?.textContent?.trim()
|
|
294
|
+
}))
|
|
295
|
+
});
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Extract Failing Audits
|
|
299
|
+
```javascript
|
|
300
|
+
JSON.stringify({
|
|
301
|
+
failed: Array.from(document.querySelectorAll('.lh-audit--fail .lh-audit__title'))
|
|
302
|
+
.map(a => a.textContent.trim().substring(0, 150)),
|
|
303
|
+
warnings: Array.from(document.querySelectorAll('.lh-audit--average .lh-audit__title'))
|
|
304
|
+
.map(a => a.textContent.trim().substring(0, 150))
|
|
305
|
+
});
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## 🏁 Exit Conditions
|
|
311
|
+
|
|
312
|
+
The PageSpeed Perfection Loop STOPS when:
|
|
313
|
+
- ✅ **ALL 4 scores = 100/100** → Perfection achieved
|
|
314
|
+
- ⚠️ **After 8 iterations** → Report remaining issues with root cause analysis
|
|
315
|
+
- 🟡 **Only server-side issues remain** (TTFB, CDN, HTTP/2) → Report as "requires server-side changes"
|
|
316
|
+
- 🟡 **Only third-party script issues remain** (Cloudflare Turnstile, analytics) → Report as "not fixable client-side"
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
*Built with ❤️ in Montpellier, France — by Laurent ROCHETTA × AI*
|