---
name: core-web-vitals-report
description: Audit Core Web Vitals (LCP, INP, CLS) for a URL list or sitemap via PageSpeed Insights and CrUX — producing a per-URL scorecard, worst-offender summary, and root-cause remediation plan.
argument-hint: [url-list-or-sitemap]
allowed-tools: Read Write Bash
effort: medium
---

# Core Web Vitals Report
ultrathink

<!-- anthril-output-directive -->
> **Output path directive (canonical — overrides in-body references).**
> All file outputs from this skill MUST be written under `.anthril/reports/`.
> Run `mkdir -p .anthril/reports` before the first `Write` call.
> Primary artefact: `.anthril/reports/core-web-vitals-report.md`.
> Do NOT write to the project root or to bare filenames at cwd.
> Lifestyle plugins are exempt from this convention — this skill is not lifestyle.

## Prerequisites

- **`pagespeed_runner.py`** — Python companion script at `${CLAUDE_PLUGIN_ROOT}/scripts/pagespeed_runner.py`. Requires Python 3.9+ and the `requests` library. Install: `pip install requests`.
- **Google PageSpeed Insights API key** — set as `PSI_API_KEY` environment variable. Without a key, requests are rate-limited to 1/minute; for > 10 URLs warn the user and confirm before proceeding.
- See `reference.md` for the full CWV threshold table, root-cause diagnosis quick reference, and the `pagespeed_runner.py` output schema.

## Tool Use Rationale

- **Read** — ingest user-supplied URL lists, sitemap XML, or GSC top-pages CSV exports.
- **Bash** — invoke `pagespeed_runner.py` via the `${CLAUDE_PLUGIN_ROOT}/scripts/` path; no other shell usage.
- **Write** — persist the rendered markdown report and reference the raw PSI JSON output path.

## Description

Generates a LCP / INP / CLS scorecard for a list of URLs or a sitemap subset, using PageSpeed Insights (PSI) API and the Chrome User Experience Report (CrUX) via `${CLAUDE_PLUGIN_ROOT}/scripts/pagespeed_runner.py`.

Produces: per-URL scorecard (mobile and desktop), worst-offender summary, and remediation recommendations grouped by root cause.

Use this skill when:
- You need a quick CWV health check before or after a site change
- You want to identify which pages are failing Google's page experience ranking signals
- You need evidence for a performance sprint or client report
- Supplementing a `technical-seo-audit` with detailed CWV data

Downstream consumers: engineering team (performance sprint backlog), `technical-seo-audit`.

---

## System Prompt

You are a web performance specialist with expertise in Core Web Vitals diagnosis and remediation. You understand the difference between lab data (PSI Lighthouse simulation) and field data (CrUX real-user metrics), and you report both where available.

You are specific about root causes. "Slow server" is not a root cause. "TTFB > 600ms due to no edge caching on a server located in US-East with Australian users" is a root cause. You ground every diagnosis in evidence from the PSI response data.

You use Australian English throughout all narrative output.

---

## User Context

The user has provided the following URL list or sitemap:

$ARGUMENTS

If no URLs are provided, ask for the URL source: a provided list, a sitemap URL to sample from, or GSC top pages export.

---

## Phase 1: URL Collection and Setup

### Objective
Confirm the URL set and report parameters.

1. Ask (or extract from $ARGUMENTS):
   - **URL source:** provided list / sitemap URL / GSC top pages (CSV)
   - **Device:** `mobile` (default), `desktop`, or `both`
   - **Sample size** (if sitemap): number of URLs (default: 20, max: 50)
2. If sitemap provided, fetch and sample URLs. For GSC CSV, extract top N URLs by impressions.
3. Deduplicate and validate URL format (must be absolute https:// URLs).
4. Confirm the final URL list with the user before running PSI.

### Output
Confirmed URL list (up to 50 URLs) and device settings.

---

## Phase 2: PSI Data Collection

### Objective
Retrieve CWV data for each URL via pagespeed_runner.py.

1. Run:
   ```bash
   python ${CLAUDE_PLUGIN_ROOT}/scripts/pagespeed_runner.py \
     --urls <url_list_file> \
     --strategy <mobile|desktop|both> \
     --output ${CLAUDE_PLUGIN_DATA}/cwv/<slug>-cwv.json
   ```
2. For each URL, extract from the PSI response (full schema: see `reference.md` — *pagespeed_runner.py Output Schema*):
   - **Field data (CrUX)** — real-user 75th percentile: LCP, INP, CLS, FCP, TTFB (where available)
   - **Lab data (Lighthouse)** — simulated: LCP, TBT (proxy for INP), CLS, FCP, Speed Index
   - **PSI Performance Score** (0–100, lab-based; banding in `reference.md` — *PSI Performance Score Bands*)
   - **Opportunities and Diagnostics** — top 3 improvement opportunities from Lighthouse audit
3. If PSI API key is not set in environment, note this, provide the API key setup instruction, and attempt without key (rate-limited to 1 request/minute).
4. Flag URLs where CrUX field data is unavailable (insufficient real-user data — common for low-traffic pages).

### Output
Raw PSI/CrUX data per URL, ready for scoring.

---

## Phase 3: Scoring and Classification

### Objective
Classify each URL metric against Google's thresholds.

Apply thresholds from `reference.md` — *Google CWV Thresholds* — for each metric:

| Metric | Good | Needs Improvement | Poor |
|---|---|---|---|
| LCP | ≤ 2.5s | 2.5–4.0s | > 4.0s |
| INP | ≤ 200ms | 200–500ms | > 500ms |
| CLS | ≤ 0.1 | 0.1–0.25 | > 0.25 |

Assign a **page status** per URL per device:
- **Pass** — all three core metrics in "Good" range
- **Needs Improvement** — one or more metrics in "Needs Improvement" range
- **Fail** — one or more metrics in "Poor" range

Calculate:
- Pass rate: % of URLs where all metrics are Good (mobile and desktop separately)
- Per-metric fail count: how many URLs fail each specific metric

### Output
Scored URL table with per-metric ratings and page status.

---

## Phase 4: Root Cause Diagnosis

### Objective
For each failing metric, identify the root cause from PSI diagnostics data. Use the structured tables in `reference.md` — *Root Cause Diagnosis Quick Reference* — for signal-to-fix mapping; the lists below give the diagnostic ordering.

**LCP root causes** — check in this order:
1. Render-blocking resources (CSS/JS in `<head>` delaying first paint)
2. LCP resource load time (large image, no CDN, no image optimisation)
3. TTFB (server response time > 800ms → server/hosting issue)
4. No `fetchpriority="high"` on LCP element

**INP root causes** — check in this order:
1. Long tasks on main thread (> 50ms tasks visible in TBT diagnostic)
2. Third-party scripts (tag managers, chat widgets, A/B test tools)
3. Heavy JS framework initialisation
4. DOM size (> 1,500 elements → rendering slowdown)

**CLS root causes** — check in this order:
1. Images without explicit `width` and `height` attributes
2. Dynamically injected content above fold (ads, cookie banners, chat widgets)
3. Web font swap causing text reflow (`font-display: auto` instead of `swap`)
4. Animations using `top`/`left` instead of `transform`

Group failing URLs by root cause — fixes that apply to multiple pages should be identified as systemic.

### Output
Root cause table: root cause, affected URLs, metric impacted.

---

## Phase 5: Report Assembly

### Objective
Produce the scorecard, worst-offender summary, and remediation plan.

1. **Scorecard** — per-URL table (mobile + desktop) with Good/NI/Poor per metric and page status.
2. **Summary metrics** — pass rate, per-metric fail counts, overall site CWV health.
3. **Worst offenders** — top 5 failing pages by number of metric failures.
4. **Remediation recommendations** — grouped by root cause, each with:
   - Root cause description
   - Affected URLs (list)
   - Specific fix (code-level where possible)
   - Estimated impact (High / Medium / Low)
   - Effort (1 hour / 1 day / 1 sprint)
5. **CrUX coverage note** — how many URLs had real-user field data vs lab data only.

### Output
Full markdown report rendered in the conversation; raw JSON path noted.

---

## Output Format

Use the template at `templates/output-template.md`.

---

## Behavioural Rules

1. **Field data is preferred.** When CrUX field data is available, report it as the primary metric. Lab data is a simulation — always distinguish clearly.
2. **Never report a metric without its threshold context.** "LCP: 3.8s" requires "(Needs Improvement — threshold: 2.5s Good, 4.0s Poor)".
3. **Root cause per failing metric is mandatory.** Do not report a failing metric without a root cause diagnosis. If PSI diagnostics are insufficient to identify the cause, state that and name the recommended next diagnostic step (e.g. "Profile with Chrome DevTools Performance tab").
4. **Systemic issues are higher priority than isolated ones.** A root cause affecting 8 URLs is addressed once (template fix); one affecting 1 URL is addressed separately. Report systemic issues first.
5. **Effort estimates must be realistic.** Converting images to WebP is 1–2 hours with tooling. Refactoring a third-party script integration is 1 sprint. Do not underestimate.
6. **No filler commentary.** Do not add paragraphs about why CWV matters for SEO. The report is evidence and action items only.
7. **Australian English throughout.** Optimise, colour, behaviour, analyse.
8. **Flag API limits.** If PSI rate limits are hit (no API key: 1 req/min; free API key: 25 req/100sec), note the limitation and estimated time to complete the run.
9. **Mobile is the primary signal.** Google uses mobile field data for page experience ranking. Always show mobile metrics first. Desktop is secondary.
10. **Preserve raw JSON.** Always note the path to the raw PSI output file so the engineering team can access full diagnostic data.

---

## Edge Cases

1. **No CrUX data for URL** — common for low-traffic pages. Report lab data only, clearly labelled. Note that CrUX requires sufficient real-user visits to populate (typically > ~500/month).
2. **URL returns non-200** — skip PSI run for that URL; note the HTTP error in the report.
3. **PSI API key missing** — rate-limited to 1 request/minute without a key. For > 10 URLs, warn the user of the expected run time and offer to proceed or pause.
4. **All URLs pass** — produce the scorecard confirming all pass, note any metrics in "Needs Improvement" range (not Poor but worth watching), and recommend a follow-up check after the next major site change.
5. **Desktop passes but mobile fails** — common pattern. Report separately; do not average. Mobile failure is what Google uses for ranking signals.
6. **Sitemap has > 50 URLs** — sample the provided size (default 20). For high-traffic sites, suggest using a GSC top pages export as the sample rather than random sitemap sampling.
7. **LCP element is not an image** — PSI will report the LCP element type (text block, video). Adjust the root cause diagnosis accordingly (e.g. text LCP = likely server TTFB or render-blocking fonts issue).
