Add comprehensive metrics and engines documentation
Complete the documentation suite with: - Deep-dive metrics reference (LCP, FCP, CLS, TBT, TTFB) - Detailed testing engines comparison (Sitespeed vs PSI) - Why TBT is the killer metric for rds.ink - How to fix each metric using Hummingbird - Score differences and when to use each engine Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
326
docs/03-metrics-reference.md
Normal file
326
docs/03-metrics-reference.md
Normal file
@@ -0,0 +1,326 @@
|
|||||||
|
# Performance Metrics Reference
|
||||||
|
|
||||||
|
Deep-dive reference for each Core Web Vital metric captured by the seo-intel system.
|
||||||
|
|
||||||
|
## The Five Metrics
|
||||||
|
|
||||||
|
| Metric | Full Name | Unit | Good | Poor | What Matters |
|
||||||
|
|--------|-----------|------|------|------|--------------|
|
||||||
|
| **LCP** | Largest Contentful Paint | milliseconds | ≤2,500ms | ≥4,000ms | When main content appears |
|
||||||
|
| **FCP** | First Contentful Paint | milliseconds | ≤1,800ms | ≥3,000ms | When ANY content appears |
|
||||||
|
| **CLS** | Cumulative Layout Shift | unitless (0-1) | ≤0.1 | ≥0.25 | How much page jumps |
|
||||||
|
| **TBT** | Total Blocking Time | milliseconds | ≤200ms | ≥600ms | JS blocking interactions |
|
||||||
|
| **TTFB** | Time to First Byte | milliseconds | ≤800ms | ≥1,800ms | Server response speed |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. LCP — Largest Contentful Paint
|
||||||
|
|
||||||
|
### Definition
|
||||||
|
The time when the **largest visible element** (image, heading, paragraph block, video) appears on screen.
|
||||||
|
|
||||||
|
### Why It Matters
|
||||||
|
Users need to see that the page is loading. LCP is the best metric for "when does the user perceive the page is starting to load?"
|
||||||
|
|
||||||
|
### Thresholds
|
||||||
|
- **≤ 2.5 seconds:** Good — user feels the page is responding
|
||||||
|
- **2.5 – 4.0 seconds:** Needs improvement
|
||||||
|
- **≥ 4.0 seconds:** Poor — user thinks the page is slow/broken
|
||||||
|
|
||||||
|
### What Affects LCP
|
||||||
|
1. **Server response time (TTFB)** — If the server is slow, everything downstream is slow
|
||||||
|
2. **Large images/videos above the fold** — Unoptimized media delays LCP
|
||||||
|
3. **Render-blocking JavaScript** — `<script>` in `<head>` delays rendering
|
||||||
|
4. **Render-blocking CSS** — Large CSS files in `<head>` delay rendering
|
||||||
|
5. **Font loading** — Web fonts block text rendering (can use `font-display: swap`)
|
||||||
|
|
||||||
|
### How to Fix
|
||||||
|
1. **Optimise TTFB** (server response)
|
||||||
|
- Cache dynamic pages
|
||||||
|
- Optimise database queries
|
||||||
|
- Use a CDN for static content
|
||||||
|
2. **Lazy-load below-the-fold images**
|
||||||
|
- Use `loading="lazy"` on `<img>` tags
|
||||||
|
- Hummingbird has automatic image lazy-loading
|
||||||
|
3. **Defer non-critical JavaScript**
|
||||||
|
- Add `defer` attribute to scripts that aren't needed for initial render
|
||||||
|
- Move analytics/tracking to the bottom
|
||||||
|
4. **Critical CSS inlining** (advanced)
|
||||||
|
- Inline the CSS needed for above-the-fold content
|
||||||
|
- Defer the rest with `<link rel="preload">`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. FCP — First Contentful Paint
|
||||||
|
|
||||||
|
### Definition
|
||||||
|
The time when the browser paints the **first piece of non-whitespace content** to the screen. This could be:
|
||||||
|
- Text
|
||||||
|
- An image
|
||||||
|
- An SVG
|
||||||
|
- A coloured background
|
||||||
|
- Anything that's not white
|
||||||
|
|
||||||
|
### Why It Matters
|
||||||
|
FCP is the user's first visual cue that the page is loading. It happens before LCP.
|
||||||
|
|
||||||
|
### Timeline Relationship
|
||||||
|
```
|
||||||
|
0ms: User clicks link
|
||||||
|
|
|
||||||
|
50ms: Server starts responding
|
||||||
|
|
|
||||||
|
144ms (TTFB): Browser receives first bytes
|
||||||
|
|
|
||||||
|
2,116ms (FCP): Browser paints first content (this page)
|
||||||
|
|
|
||||||
|
2,500ms (LCP): Browser paints largest content
|
||||||
|
|
|
||||||
|
4,000ms: Page is fully interactive
|
||||||
|
```
|
||||||
|
|
||||||
|
### Thresholds
|
||||||
|
- **≤ 1.8 seconds:** Good
|
||||||
|
- **1.8 – 3.0 seconds:** Needs improvement (this page is at 2.1s)
|
||||||
|
- **≥ 3.0 seconds:** Poor
|
||||||
|
|
||||||
|
### What Affects FCP
|
||||||
|
1. **TTFB** — Server has to respond first
|
||||||
|
2. **HTML parsing** — Browser must parse HTML to find content
|
||||||
|
3. **Render-blocking resources** — CSS/JS in `<head>` delay rendering
|
||||||
|
4. **Font loading** — If fonts are slow, text doesn't paint until fonts arrive
|
||||||
|
|
||||||
|
### How to Fix
|
||||||
|
Same as LCP:
|
||||||
|
1. Optimise TTFB
|
||||||
|
2. Defer render-blocking resources
|
||||||
|
3. Lazy-load heavy assets
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. CLS — Cumulative Layout Shift
|
||||||
|
|
||||||
|
### Definition
|
||||||
|
**Measure of unwanted layout changes** after the page is visually complete.
|
||||||
|
|
||||||
|
Example: You're reading an article, about to click a button, and an ad loads above the button, pushing the button down. You accidentally click the ad instead. That's layout shift.
|
||||||
|
|
||||||
|
### Why It Matters
|
||||||
|
CLS directly impacts **user experience frustration**. Unexpected layout changes are one of the most annoying things on the web.
|
||||||
|
|
||||||
|
### Measurement
|
||||||
|
```
|
||||||
|
CLS = sum of all individual layout shifts
|
||||||
|
|
||||||
|
Each shift is: (fraction of viewport moved) × (distance moved)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
- Ad loads, pushes button down by 50px
|
||||||
|
- Viewport is 800px tall
|
||||||
|
- Shift score = (0.5 × 800) / 800 = 0.5
|
||||||
|
|
||||||
|
If this happens once, CLS = 0.5
|
||||||
|
If it happens three times (each 0.5), CLS = 1.5
|
||||||
|
```
|
||||||
|
|
||||||
|
Shifts that happen > 500ms after user input are excluded (they don't surprise the user).
|
||||||
|
|
||||||
|
### Thresholds
|
||||||
|
- **≤ 0.1:** Good — page is stable
|
||||||
|
- **0.1 – 0.25:** Needs improvement — some shifts happening
|
||||||
|
- **≥ 0.25:** Poor — page is jumpy
|
||||||
|
|
||||||
|
### rds.ink Status: 0.0 = PERFECT ✓
|
||||||
|
|
||||||
|
This page does NOT shift after load. The images load lazily, product cards maintain their size, nothing pops up. Great job.
|
||||||
|
|
||||||
|
### What Causes CLS
|
||||||
|
1. **Unset image/video dimensions** — Browser doesn't know how much space to reserve
|
||||||
|
2. **Ads/widgets loading after page render** — Third-party content shifts layout
|
||||||
|
3. **Custom fonts** — Text changes size when font finishes loading
|
||||||
|
4. **Embeds/iframes** — External content pushes layout
|
||||||
|
5. **Animations that move elements** — Animation changing `top` / `left` / `margin`
|
||||||
|
|
||||||
|
### How to Fix
|
||||||
|
1. **Set dimensions on images**
|
||||||
|
```html
|
||||||
|
<img src="..." width="800" height="600" />
|
||||||
|
<!-- or in CSS -->
|
||||||
|
img { aspect-ratio: 800 / 600; }
|
||||||
|
```
|
||||||
|
2. **Reserve space for ads/lazy content**
|
||||||
|
```html
|
||||||
|
<div style="width: 300px; height: 250px;">
|
||||||
|
<!-- ad will load here -->
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
3. **Use `font-display: swap`**
|
||||||
|
```css
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Custom';
|
||||||
|
src: url(...);
|
||||||
|
font-display: swap; /* show fallback first, swap when custom loads */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
4. **Animations: use `transform` instead of `top`/`left`**
|
||||||
|
```css
|
||||||
|
/* GOOD: transform doesn't trigger layout recalc */
|
||||||
|
@keyframes slide {
|
||||||
|
from { transform: translateX(0); }
|
||||||
|
to { transform: translateX(100px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* BAD: left does trigger layout recalc */
|
||||||
|
@keyframes slide {
|
||||||
|
from { left: 0; }
|
||||||
|
to { left: 100px; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. TBT — Total Blocking Time 🔴 **MOST COMMON PROBLEM**
|
||||||
|
|
||||||
|
### Definition
|
||||||
|
**How long JavaScript is executing on the main thread, blocking all user interactions.**
|
||||||
|
|
||||||
|
The browser can only do one thing at a time on the main thread:
|
||||||
|
- Parse HTML
|
||||||
|
- Execute JavaScript
|
||||||
|
- Render CSS
|
||||||
|
- Handle user input
|
||||||
|
|
||||||
|
If JavaScript is running, the browser **cannot** respond to clicks, scrolls, or keypresses.
|
||||||
|
|
||||||
|
### Why It Matters
|
||||||
|
A page with high TBT **feels frozen**. User clicks a button, nothing happens for 1+ seconds. The page is technically loaded, but unusable.
|
||||||
|
|
||||||
|
### Measurement
|
||||||
|
```
|
||||||
|
JavaScript task execution timeline:
|
||||||
|
|
||||||
|
0ms ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 200ms (long task, blocks for 200ms)
|
||||||
|
User clicks button during this
|
||||||
|
but browser doesn't respond
|
||||||
|
|
||||||
|
200ms (JavaScript done) → Click now registers (if still there)
|
||||||
|
```
|
||||||
|
|
||||||
|
TBT = sum of all "blocking" time. A task is "blocking" if it takes > 50ms.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
- 5 JavaScript tasks, each 100ms = TBT of 5 × 50ms = 250ms blocking
|
||||||
|
- 2 JavaScript tasks, each 1,000ms = TBT of 2 × 950ms = 1,900ms blocking
|
||||||
|
|
||||||
|
### Thresholds
|
||||||
|
- **≤ 200ms:** Good — page feels snappy
|
||||||
|
- **200 – 600ms:** Needs improvement — noticeable sluggishness
|
||||||
|
- **≥ 600ms:** Poor — page feels frozen
|
||||||
|
|
||||||
|
### rds.ink Status: 1,807ms = CRITICAL 🔴
|
||||||
|
|
||||||
|
The page has **1,800ms of JavaScript execution blocking interactions**. During page load, the user cannot interact for 1.8 seconds.
|
||||||
|
|
||||||
|
This is why the score is 77 instead of 95.
|
||||||
|
|
||||||
|
### What Causes High TBT
|
||||||
|
1. **Large JavaScript bundles** (1.8 MB on this page)
|
||||||
|
2. **Synchronous JavaScript execution** — No chunking or deferring
|
||||||
|
3. **Too many plugins** — Each plugin adds code to parse/execute
|
||||||
|
4. **Unoptimised heavy libraries** — jQuery, older frameworks
|
||||||
|
5. **No code-splitting** — Entire app loads upfront instead of as-needed
|
||||||
|
|
||||||
|
### How to Fix (for WordPress/Hummingbird)
|
||||||
|
1. **Defer non-critical JavaScript** (add `defer` attribute)
|
||||||
|
- Page renders first
|
||||||
|
- Scripts load in background
|
||||||
|
- TBT moves to after page is interactive
|
||||||
|
2. **Lazy-load heavy plugins** (load only when needed)
|
||||||
|
- Gallery/lightbox: load when user clicks product
|
||||||
|
- Booking widget: load only on booking page
|
||||||
|
3. **Disable unused plugins** (every plugin = more JS)
|
||||||
|
4. **Code-split large bundles** (Webpack/bundler feature)
|
||||||
|
- Don't load everything upfront
|
||||||
|
- Load only what's visible
|
||||||
|
5. **Minify/compress JavaScript** (reduce parse time)
|
||||||
|
|
||||||
|
### Expected Impact of Fixes
|
||||||
|
- Current: 1,807ms blocking
|
||||||
|
- After defer JS: ~400ms
|
||||||
|
- After lazy-load: ~150ms
|
||||||
|
- After disabling unused: ~100ms
|
||||||
|
- **Target: <200ms**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. TTFB — Time to First Byte
|
||||||
|
|
||||||
|
### Definition
|
||||||
|
The time from when the browser makes a request until the server sends the first byte of the response.
|
||||||
|
|
||||||
|
```
|
||||||
|
User hits link at 0ms
|
||||||
|
↓
|
||||||
|
0-50ms: Network latency
|
||||||
|
↓
|
||||||
|
50-100ms: Server processes request
|
||||||
|
↓
|
||||||
|
100-144ms: Server sends response (for this page)
|
||||||
|
↓ This is TTFB
|
||||||
|
144ms: Browser receives first byte
|
||||||
|
```
|
||||||
|
|
||||||
|
### Why It Matters
|
||||||
|
TTFB is a **server-side metric**. It measures how fast your infrastructure is.
|
||||||
|
|
||||||
|
Everything downstream depends on TTFB. You can't optimise FCP if TTFB is 2 seconds.
|
||||||
|
|
||||||
|
### Thresholds
|
||||||
|
- **≤ 0.8 seconds:** Good
|
||||||
|
- **0.8 – 1.8 seconds:** Needs improvement
|
||||||
|
- **≥ 1.8 seconds:** Poor
|
||||||
|
|
||||||
|
### rds.ink Status: 144ms = EXCELLENT ✓
|
||||||
|
|
||||||
|
The server responds in 144ms. This is good. Not the bottleneck.
|
||||||
|
|
||||||
|
### What Affects TTFB
|
||||||
|
1. **Server processing time** — Database queries, rendering, etc.
|
||||||
|
2. **Network latency** — Distance from client to server
|
||||||
|
3. **Server hardware** — CPU/RAM/I/O speed
|
||||||
|
4. **Caching** — Is the page/response cached?
|
||||||
|
5. **DNS resolution** — Domain lookup time
|
||||||
|
|
||||||
|
### How to Fix
|
||||||
|
1. **Enable page caching** (Hummingbird)
|
||||||
|
- Cached pages: TTFB 50ms
|
||||||
|
- Uncached: TTFB 144ms
|
||||||
|
2. **Optimise database queries** (most common bottleneck)
|
||||||
|
- Use database indexing
|
||||||
|
- Avoid N+1 queries
|
||||||
|
- Query Monitor plugin helps diagnose
|
||||||
|
3. **Use a CDN for static assets**
|
||||||
|
- CSS, JS, images served from fast edge servers
|
||||||
|
4. **Upgrade hosting** (if server is slow)
|
||||||
|
- More CPU cores
|
||||||
|
- Faster SSD storage
|
||||||
|
- Better network connection
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary Table
|
||||||
|
|
||||||
|
| Metric | Causes | Quick Fixes |
|
||||||
|
|--------|--------|-----------|
|
||||||
|
| **LCP** | Slow TTFB, lazy images, render-blocking JS | Defer JS, optimize TTFB |
|
||||||
|
| **FCP** | Slow TTFB, render-blocking resources | Defer JS, lazy load |
|
||||||
|
| **CLS** | Unset dimensions, ads, fonts, animations | Set dimensions, use `transform` |
|
||||||
|
| **TBT** (THE PROBLEM) | Large JS, too many plugins, sync code | Defer JS, lazy-load plugins, disable unused |
|
||||||
|
| **TTFB** | Slow server, no cache, slow DB | Enable cache, optimize queries |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
See also:
|
||||||
|
- [Score Calculation](02-score-calculation.md) — How these metrics become a 0-100 score
|
||||||
|
- [Testing Engines](04-testing-engines.md) — How metrics are captured
|
||||||
|
- [Case Study: rds.ink 77](../case-studies/rds-77-score.md) — Real example with fixes
|
||||||
333
docs/04-testing-engines.md
Normal file
333
docs/04-testing-engines.md
Normal file
@@ -0,0 +1,333 @@
|
|||||||
|
# Testing Engines: Sitespeed vs PSI
|
||||||
|
|
||||||
|
Detailed comparison of the two independent engines used to measure performance.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The seo-intel system uses **two different testing engines** in parallel:
|
||||||
|
|
||||||
|
1. **Sitespeed.io** — Real-browser testing with HAR waterfall
|
||||||
|
2. **Google PageSpeed Insights (PSI)** — Official Lighthouse audits
|
||||||
|
|
||||||
|
This dual approach captures:
|
||||||
|
- Real-user metrics via browser instrumentation (Sitespeed)
|
||||||
|
- Official Google scores + recommendations (PSI)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sitespeed.io
|
||||||
|
|
||||||
|
### What It Is
|
||||||
|
An open-source performance testing framework that runs a **headless Chrome browser** to measure page performance in real-world conditions.
|
||||||
|
|
||||||
|
**Docker image:** `sitespeedio/sitespeed.io:40.4.0`
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
|
||||||
|
```
|
||||||
|
1. User clicks "Test" for https://rds.ink/endangered
|
||||||
|
↓
|
||||||
|
2. Sitespeed starts Docker container with headless Chrome
|
||||||
|
↓
|
||||||
|
3. Browser loads page (3 times, N=3)
|
||||||
|
- Run 1: LCP=2300ms, FCP=2100ms, TBT=1800ms
|
||||||
|
- Run 2: LCP=2500ms, FCP=2120ms, TBT=1850ms
|
||||||
|
- Run 3: LCP=2400ms, FCP=2110ms, TBT=1780ms
|
||||||
|
↓
|
||||||
|
4. Sitespeed takes the MEDIAN of the three runs
|
||||||
|
- LCP = 2400ms (middle value)
|
||||||
|
- FCP = 2110ms
|
||||||
|
- TBT = 1800ms
|
||||||
|
↓
|
||||||
|
5. Browser exports HAR (HTTP Archive) file
|
||||||
|
- Contains: every resource, timing, size
|
||||||
|
- JSON file with full waterfall
|
||||||
|
↓
|
||||||
|
6. Sitespeed parses HAR
|
||||||
|
- Extracts CWV metrics
|
||||||
|
- Calculates page weight
|
||||||
|
- Identifies resources
|
||||||
|
↓
|
||||||
|
7. Approximates 0-100 score from thresholds
|
||||||
|
- NOT official Lighthouse (no Lighthouse plugin)
|
||||||
|
- But uses same CWV thresholds as Lighthouse
|
||||||
|
```
|
||||||
|
|
||||||
|
### Metrics Captured
|
||||||
|
|
||||||
|
**Core Web Vitals:**
|
||||||
|
- LCP (Largest Contentful Paint)
|
||||||
|
- FCP (First Contentful Paint)
|
||||||
|
- CLS (Cumulative Layout Shift)
|
||||||
|
- TBT (Total Blocking Time)
|
||||||
|
- TTFB (Time to First Byte)
|
||||||
|
- INP (Interaction to Next Paint) — Not captured in v40
|
||||||
|
|
||||||
|
**Page breakdown:**
|
||||||
|
- Total page size (bytes)
|
||||||
|
- Image bytes
|
||||||
|
- JavaScript bytes
|
||||||
|
- CSS bytes
|
||||||
|
- Font bytes
|
||||||
|
- Request count
|
||||||
|
|
||||||
|
**Resource list:**
|
||||||
|
- Every HTTP request made
|
||||||
|
- URL, type (script/image/stylesheet/font/xhr)
|
||||||
|
- Size, timing
|
||||||
|
|
||||||
|
### Advantages
|
||||||
|
- ✅ **Real browser** — Chrome's actual instrumentation
|
||||||
|
- ✅ **Full HAR** — See every resource, identify bottlenecks
|
||||||
|
- ✅ **Consistent** — Can run anytime, same environment
|
||||||
|
- ✅ **Resource timing** — Measure individual script/image load times
|
||||||
|
- ✅ **Median metrics** — Run 3 times, use median (more stable than single run)
|
||||||
|
|
||||||
|
### Disadvantages
|
||||||
|
- ❌ **No Lighthouse** — Score is approximated, not official
|
||||||
|
- ❌ **Slower** — 60 seconds per device
|
||||||
|
- ❌ **No opportunities** — Doesn't tell you "fix this"
|
||||||
|
- ❌ **No INP metric** — v40 doesn't capture Interaction to Next Paint
|
||||||
|
- ❌ **Approximate score** — Different algorithm than real Lighthouse
|
||||||
|
|
||||||
|
### Device Modes
|
||||||
|
|
||||||
|
**Mobile mode** (default):
|
||||||
|
```
|
||||||
|
--mobile --connectivity 4g
|
||||||
|
```
|
||||||
|
- Emulates Moto G4 device (412x732 viewport)
|
||||||
|
- 4G throttling (simulates real 4G speeds)
|
||||||
|
- Mobile user agent
|
||||||
|
- **Duration:** ~60s
|
||||||
|
|
||||||
|
**Desktop mode:**
|
||||||
|
```
|
||||||
|
--browsertime.connectivity native --browsertime.viewPort 1366x768 --browsertime.userAgent "Chrome Windows"
|
||||||
|
```
|
||||||
|
- 1366x768 viewport (typical laptop)
|
||||||
|
- Native connectivity (no throttling)
|
||||||
|
- Desktop Chrome user agent
|
||||||
|
- **Duration:** ~60s
|
||||||
|
|
||||||
|
### Output Files
|
||||||
|
|
||||||
|
Stored at: `/tmp/sitespeed-output/{run_id}/pages/{domain}/data/`
|
||||||
|
|
||||||
|
- `browsertime.har` — Full HTTP Archive (JSON)
|
||||||
|
- `browsertime.json` — Detailed metrics (also JSON)
|
||||||
|
- `screenShots/` — Video/screenshots of page load
|
||||||
|
|
||||||
|
### Score Calculation (Sitespeed v40)
|
||||||
|
|
||||||
|
Since Sitespeed v40 doesn't run Lighthouse, it approximates the score:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# src/perf/sitespeed.py:_approx_score()
|
||||||
|
|
||||||
|
_THRESHOLDS = {
|
||||||
|
"lcp": (2500, 4000),
|
||||||
|
"fcp": (1800, 3000),
|
||||||
|
"cls": (0.1, 0.25),
|
||||||
|
"tbt": (200, 600),
|
||||||
|
"ttfb": (800, 1800),
|
||||||
|
}
|
||||||
|
|
||||||
|
# For each metric:
|
||||||
|
if value <= good:
|
||||||
|
score = 100
|
||||||
|
elif value >= poor:
|
||||||
|
score = 30
|
||||||
|
else:
|
||||||
|
ratio = (value - good) / (poor - good)
|
||||||
|
score = 100 - (ratio * 70)
|
||||||
|
|
||||||
|
# Final score = average of all metric scores
|
||||||
|
performance_score = mean([lcp_score, fcp_score, cls_score, tbt_score, ttfb_score])
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important:** This is NOT the real Lighthouse score. It's an approximation for trend tracking.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Google PageSpeed Insights (PSI)
|
||||||
|
|
||||||
|
### What It Is
|
||||||
|
Google's official **Lighthouse audit** service. You submit a URL and Google runs Lighthouse against it.
|
||||||
|
|
||||||
|
**API endpoint:** `https://www.googleapis.com/pagespeedonline/v5/runPagespeed`
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
|
||||||
|
```
|
||||||
|
1. seo-intel calls Google's API
|
||||||
|
GET /pagespeedonline/v5/runPagespeed?url=...&strategy=mobile
|
||||||
|
↓
|
||||||
|
2. Google spins up Lighthouse
|
||||||
|
- Full audit: performance, accessibility, best practices, SEO, PWA
|
||||||
|
- We only care about "performance" category
|
||||||
|
↓
|
||||||
|
3. Lighthouse runs and scores 0-100
|
||||||
|
- This is the OFFICIAL score
|
||||||
|
- Uses Google's real Lighthouse algorithm
|
||||||
|
↓
|
||||||
|
4. Lighthouse audit results returned
|
||||||
|
- Performance score (0-100)
|
||||||
|
- All audit items (100+ audits)
|
||||||
|
- Opportunities (what to fix + savings)
|
||||||
|
↓
|
||||||
|
5. seo-intel parses response
|
||||||
|
- Extracts score
|
||||||
|
- Extracts opportunities
|
||||||
|
- Calculates potential savings (ms + bytes)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Metrics Captured
|
||||||
|
|
||||||
|
**Official performance score** (0-100)
|
||||||
|
- This is what Google reports
|
||||||
|
- Different algorithm than Sitespeed approximation
|
||||||
|
|
||||||
|
**Opportunities:**
|
||||||
|
- "Reduce unused JavaScript" → 400ms savings, 150KB reduction
|
||||||
|
- "Minify CSS" → 50ms, 20KB
|
||||||
|
- "Lazy load offscreen images" → 200ms, 500KB
|
||||||
|
- "Eliminate render-blocking resources" → 300ms
|
||||||
|
- (and ~20 more opportunities)
|
||||||
|
|
||||||
|
**Same CWV metrics as Sitespeed:**
|
||||||
|
- LCP, FCP, CLS, TBT, TTFB
|
||||||
|
|
||||||
|
### Advantages
|
||||||
|
- ✅ **Official Lighthouse** — What Google actually scores
|
||||||
|
- ✅ **Opportunities** — Specific recommendations on what to fix
|
||||||
|
- ✅ **Savings estimates** — How much you'd save per fix
|
||||||
|
- ✅ **Comprehensive audit** — 100+ checks across performance, UX, SEO
|
||||||
|
- ✅ **Credibility** — "Google says you score 95"
|
||||||
|
|
||||||
|
### Disadvantages
|
||||||
|
- ❌ **No HAR** — Can't see individual resource timings
|
||||||
|
- ❌ **Slower** — 30-90 seconds per device (depends on Google's load)
|
||||||
|
- ❌ **Rate-limited** — ~25k tests/day without API key
|
||||||
|
- ❌ **Slower infrastructure** — Google's API is slower than local Sitespeed
|
||||||
|
- ❌ **No resource breakdown** — Can't identify which JS file is slow
|
||||||
|
|
||||||
|
### API Key
|
||||||
|
|
||||||
|
Optional. Without it, you get ~25,000 tests/day. With it, you get much higher limits.
|
||||||
|
|
||||||
|
**Where to set:**
|
||||||
|
- `.env` file: `PSI_API_KEY=...`
|
||||||
|
- Or environment variable: `export PSI_API_KEY=...`
|
||||||
|
|
||||||
|
**Where to get:**
|
||||||
|
1. Google Cloud Console
|
||||||
|
2. Create project
|
||||||
|
3. Enable PageSpeed Insights API
|
||||||
|
4. Create API key
|
||||||
|
5. Set in `.env`
|
||||||
|
|
||||||
|
If not set, seo-intel still works but you might hit rate limits on very high-volume testing.
|
||||||
|
|
||||||
|
### Score Differences from Sitespeed
|
||||||
|
|
||||||
|
PSI score ≠ Sitespeed score because they use different algorithms:
|
||||||
|
|
||||||
|
| Aspect | Sitespeed Score | PSI Score |
|
||||||
|
|--------|---|---|
|
||||||
|
| Source | Approximated from thresholds | Official Lighthouse |
|
||||||
|
| Algorithm | Linear interpolation | Complex weighting |
|
||||||
|
| Weightings | Equal (each metric = 1/5) | Weighted (some metrics matter more) |
|
||||||
|
| Audits | None | 100+ audits |
|
||||||
|
| Opportunities | None | Yes (what to fix) |
|
||||||
|
| Example | 77 (this page) | 95 (estimated) |
|
||||||
|
|
||||||
|
**Sitespeed 77** means: directional score, TBT is the killer
|
||||||
|
**PSI 95** means: official Google score, page is good but TBT hurts it slightly
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Comparison Table
|
||||||
|
|
||||||
|
| Feature | Sitespeed | PSI |
|
||||||
|
|---------|-----------|-----|
|
||||||
|
| **Real browser** | ✅ Headless Chrome | ✅ Lighthouse (Chrome) |
|
||||||
|
| **Duration** | 60s | 30-90s |
|
||||||
|
| **HAR output** | ✅ Full | ❌ Limited |
|
||||||
|
| **Resource timing** | ✅ Per-resource | ❌ Aggregate only |
|
||||||
|
| **Official score** | ❌ Approximated | ✅ Real Lighthouse |
|
||||||
|
| **Opportunities** | ❌ None | ✅ Full audit |
|
||||||
|
| **Savings estimates** | ❌ No | ✅ Yes (ms + bytes) |
|
||||||
|
| **CWV metrics** | ✅ LCP, FCP, CLS, TBT, TTFB, INP | ✅ Same |
|
||||||
|
| **Cost** | Free (Docker) | Free (25k/day) or API key |
|
||||||
|
| **Best for** | Trend tracking, waterfall analysis | Official benchmarking, what to fix |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Which Score Should You Use?
|
||||||
|
|
||||||
|
**For trend tracking:**
|
||||||
|
Use **Sitespeed score** (77). It's fast, local, consistent. You can test weekly and see if score improves over time.
|
||||||
|
|
||||||
|
**For official reporting:**
|
||||||
|
Use **PSI score** (95). It's what Google officially scores you. Client-friendly, credible.
|
||||||
|
|
||||||
|
**For diagnosing problems:**
|
||||||
|
Use **individual metrics** (TBT=1,807ms). This tells you exactly what's broken. Focus on the worst metric first.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How They Work Together
|
||||||
|
|
||||||
|
The dual-engine approach gives you:
|
||||||
|
|
||||||
|
1. **Sitespeed** finds the bottleneck (TBT=1,807ms is the killer)
|
||||||
|
2. **Sitespeed HAR** shows you the resources causing TBT (JavaScript files)
|
||||||
|
3. **PSI** tells you how to fix it (opportunities: defer JS, lazy-load, etc.)
|
||||||
|
4. **PSI score** tells you the official Google score (95)
|
||||||
|
5. **Trends** show if your fixes actually work (score 77 → 88 → 95)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Docker Details (Sitespeed)
|
||||||
|
|
||||||
|
### Image Details
|
||||||
|
```
|
||||||
|
Docker Hub: sitespeedio/sitespeed.io:40.4.0
|
||||||
|
Size: ~1.5 GB
|
||||||
|
Base: Node.js + Chrome
|
||||||
|
Updated: May 2026
|
||||||
|
```
|
||||||
|
|
||||||
|
### Why v40.4.0?
|
||||||
|
- Latest stable version (verified 2026-05-13)
|
||||||
|
- Previous versions have bugs or missing metrics
|
||||||
|
- Pinned version ensures reproducible results
|
||||||
|
|
||||||
|
### How seo-intel Runs It
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --rm \
|
||||||
|
--shm-size=1g \
|
||||||
|
-v /tmp/sitespeed-output:/sitespeed.io \
|
||||||
|
sitespeedio/sitespeed.io:40.4.0 \
|
||||||
|
https://rds.ink/endangered \
|
||||||
|
--mobile --connectivity 4g \
|
||||||
|
--n 3 \
|
||||||
|
--outputFolder /sitespeed.io/{run_id} \
|
||||||
|
--summary --summary-detail
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key flags:**
|
||||||
|
- `--rm` — Delete container after run (clean up)
|
||||||
|
- `--shm-size=1g` — Allocate 1GB shared memory for Chrome
|
||||||
|
- `-v` — Mount output directory so we can read the HAR
|
||||||
|
- `--n 3` — Run 3 iterations (use median)
|
||||||
|
- `--summary` — Print summary to stdout
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
See also:
|
||||||
|
- [System Architecture](01-architecture.md) — How engines fit in the larger system
|
||||||
|
- [Score Calculation](02-score-calculation.md) — How Sitespeed approximates scores
|
||||||
|
- [Metrics Reference](03-metrics-reference.md) — What each metric means
|
||||||
Reference in New Issue
Block a user