skills/pagespeed-audit/SKILL.md
Collect and analyze Core Web Vitals and PageSpeed scores across multiple URLs using the PageSpeed Insights API and RUM data aggregation. Use when "running PageSpeed tests", "Core Web Vitals audit", "performance benchmarking", or "CWV analysis".
npx skillsauth add paolomoz/skills pagespeed-auditInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
| Category | Trigger | Complexity | Source | |----------|---------|------------|--------| | audit | "running PageSpeed tests", "Core Web Vitals audit", "performance benchmarking", "CWV analysis" | Medium | 5 projects |
Aggregate and analyze Core Web Vitals from Real User Monitoring (RUM) bundles and the PageSpeed Insights API. Produces per-URL performance profiles with device segmentation, traffic analysis, and cross-referenced quality issues. The output feeds directly into report-hub-generator for stakeholder reporting and into site-auditor for traffic-weighted content prioritization.
The skill supports two data sources. Use whichever is available; if both are available, prefer RUM data (it represents real user experience) and supplement with PSI API data for lab metrics.
| Source | What It Provides | When to Use | |--------|-----------------|-------------| | RUM Bundles | Real user CWV, traffic data, referrers, device types | When the user has access to RUM collection (e.g., Adobe RUM, custom RUM) | | PageSpeed Insights API | Lab CWV, Lighthouse scores, optimization suggestions | When no RUM data is available, or for supplementary lab benchmarks |
RUM data arrives as an array of bundles, each representing a single page view with weighted sampling.
// Each bundle represents one or more page views (weight-based sampling)
{
"id": "abc123",
"url": "https://example.com/blog/performance-tips",
"weight": 100, // This bundle represents ~100 actual page views
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X)...",
"events": [
{ "checkpoint": "cwv-lcp", "value": 2340, "source": "img.hero-image" },
{ "checkpoint": "cwv-cls", "value": 0.042 },
{ "checkpoint": "cwv-inp", "value": 156, "source": "button.add-to-cart" },
{ "checkpoint": "cwv-ttfb", "value": 380 },
{ "checkpoint": "click", "source": "a.cta-primary", "target": "/pricing" },
{ "checkpoint": "enter", "source": "https://google.com/search?q=..." },
{ "checkpoint": "404" },
{ "checkpoint": "missingresource", "source": "/scripts/legacy-widget.js" }
]
}
Key checkpoint types:
cwv-lcp: Largest Contentful Paint in millisecondscwv-cls: Cumulative Layout Shift (unitless score)cwv-inp: Interaction to Next Paint in millisecondscwv-ttfb: Time to First Byte in millisecondsclick: User click event with source selector and target URLenter: Page entry with referrer source404: Page returned a 404 statusmissingresource: A subresource (JS, CSS, image) failed to loadFor each unique URL, aggregate CWV metrics using the weight field:
function aggregateMetrics(bundles) {
const urlMap = new Map()
for (const bundle of bundles) {
const path = new URL(bundle.url).pathname
if (!urlMap.has(path)) {
urlMap.set(path, { views: 0, lcp: [], cls: [], inp: [], ttfb: [] })
}
const entry = urlMap.get(path)
entry.views += bundle.weight
for (const event of bundle.events) {
if (event.checkpoint === 'cwv-lcp') entry.lcp.push({ value: event.value, weight: bundle.weight })
if (event.checkpoint === 'cwv-cls') entry.cls.push({ value: event.value, weight: bundle.weight })
if (event.checkpoint === 'cwv-inp') entry.inp.push({ value: event.value, weight: bundle.weight })
if (event.checkpoint === 'cwv-ttfb') entry.ttfb.push({ value: event.value, weight: bundle.weight })
}
}
return urlMap
}
For each metric array, compute:
sum(value * weight) / sum(weight)Classify each metric using Google's official thresholds:
| Metric | Good | Needs Improvement | Poor | |--------|------|-------------------|------| | LCP | <= 2500ms | 2500-4000ms | > 4000ms | | CLS | <= 0.1 | 0.1-0.25 | > 0.25 | | INP | <= 200ms | 200-500ms | > 500ms | | TTFB | <= 800ms | 800-1800ms | > 1800ms |
A page passes Core Web Vitals if LCP, CLS, and INP are all in the "Good" range at the p75. TTFB is an auxiliary metric -- it is not part of the official CWV assessment but is a strong diagnostic signal.
function assessCWV(metrics) {
return {
lcp: { value: metrics.lcp.p75, rating: metrics.lcp.p75 <= 2500 ? 'good' : metrics.lcp.p75 <= 4000 ? 'needs-improvement' : 'poor' },
cls: { value: metrics.cls.p75, rating: metrics.cls.p75 <= 0.1 ? 'good' : metrics.cls.p75 <= 0.25 ? 'needs-improvement' : 'poor' },
inp: { value: metrics.inp.p75, rating: metrics.inp.p75 <= 200 ? 'good' : metrics.inp.p75 <= 500 ? 'needs-improvement' : 'poor' },
ttfb: { value: metrics.ttfb.p75, rating: metrics.ttfb.p75 <= 800 ? 'good' : metrics.ttfb.p75 <= 1800 ? 'needs-improvement' : 'poor' },
passing: metrics.lcp.p75 <= 2500 && metrics.cls.p75 <= 0.1 && metrics.inp.p75 <= 200
}
}
Classify each bundle's userAgent into one of four device categories:
| Category | Detection Pattern |
|----------|-----------------|
| mobile | Contains Mobile, iPhone, Android (but not Tablet) |
| tablet | Contains iPad, Tablet, Android with large viewport hints |
| desktop | Does not match mobile or tablet patterns, not a bot |
| bot | Contains bot, crawler, spider, Googlebot, Bingbot, lighthouse |
Exclude bot traffic from CWV calculations entirely -- bot performance data is not representative of user experience. Track bot traffic separately for crawl budget analysis.
Produce separate CWV assessments for mobile and desktop. Mobile metrics are typically worse and are the primary signal Google uses for ranking.
Extract non-CWV insights from the RUM bundles:
Aggregate enter checkpoint sources by domain. Group into categories: organic search (google, bing, duckduckgo), social (twitter, facebook, linkedin), direct (no referrer), and other.
Aggregate click checkpoints by target URL. This reveals the most-used navigation paths and CTAs.
404 checkpoint. Cross-reference with site-auditor to identify broken links that are actively generating user errors.missingresource checkpoints. Aggregate by resource URL to find globally missing assets.If site-auditor has been run and data/audit/analysis.json exists, enrich the performance data:
Write the structured output to data/audit/performance.json:
{
"meta": {
"domain": "example.com",
"period": { "start": "2024-12-01", "end": "2024-12-15" },
"generatedAt": "2024-12-15T10:30:00Z",
"source": "rum",
"bundleCount": 14523
},
"summary": {
"totalViews": 1452300,
"uniqueUrls": 847,
"cwvPassRate": 0.72,
"totalClicks": 328400,
"devices": {
"mobile": { "views": 870000, "cwvPassRate": 0.65 },
"desktop": { "views": 540000, "cwvPassRate": 0.82 },
"tablet": { "views": 42300, "cwvPassRate": 0.71 }
}
},
"pages": [
{
"path": "/blog/performance-tips",
"views": 12400,
"cwv": {
"lcp": { "avg": 2100, "p75": 2680, "samples": 124, "rating": "needs-improvement" },
"cls": { "avg": 0.04, "p75": 0.08, "samples": 124, "rating": "good" },
"inp": { "avg": 120, "p75": 180, "samples": 98, "rating": "good" },
"ttfb": { "avg": 340, "p75": 520, "samples": 124, "rating": "good" }
},
"passing": false,
"lcpElement": "img.hero-image",
"topReferrers": ["google.com", "twitter.com"],
"topClicks": ["/pricing", "/docs/getting-started"]
}
],
"issues": {
"fourOhFourPages": [
{ "path": "/old-page", "views": 340, "topReferrers": ["google.com"] }
],
"poorCwv": [
{ "path": "/products/gallery", "metric": "lcp", "p75": 5200, "views": 8900 }
],
"globalMissingResources": [
{ "resource": "/scripts/legacy-widget.js", "affectedPages": 124, "affectedViews": 45000 }
]
}
}
Summarize the performance audit with these sections:
Always frame performance numbers in user impact terms: "12,400 users per month experience an LCP of 2.7 seconds on this page" rather than just reporting the raw number.
| Problem | Cause | Fix |
|---------|-------|-----|
| CWV metrics are all null | Bundles contain no cwv-* checkpoints | RUM library may not be collecting CWV. Check that the CWV collection script is installed. |
| Mobile pass rate is much lower than desktop | Normal -- mobile networks are slower | Focus optimization on mobile. Consider separate mobile/desktop analysis. |
| p75 is much higher than average | Long-tail distribution with outliers | This is expected. p75 is the correct metric; average understates user pain. |
| Bot traffic is inflating view counts | Bots not filtered | Apply device segmentation (Step 4) and exclude bot category from all metrics. |
| Missing resource affects 0 pages | Resource URL does not match any page | The resource may be loaded dynamically; check if it is a third-party script. |
| PSI API returns rate limit errors | Too many requests in a short period | Batch requests with 1-second delays between calls. PSI API allows 25,000 queries/day with an API key. |
development
Generate artistic infographics from any topic. Runs the Sumi pipeline (analyze → structure → craft prompt → generate image) entirely within Claude Code. Use when "generate infographic", "create infographic", "sumi", "make an infographic about", or "visualize topic".
tools
Implement Server-Sent Events streaming from Cloudflare Workers to browser clients with reconnection, state persistence, and progress tracking. Use when building "SSE streaming", "real-time updates", "server push", or "event streaming".
development
Audit websites by cross-referencing query indexes, sitemaps, and navigation to identify content gaps, stale pages, missing metadata, and quality issues. Use when "auditing a website", "finding content gaps", "site quality audit", or "content inventory analysis".
data-ai
Track user session context across multi-turn interactions using browser sessionStorage and server-side KV caching with TTL. Use when implementing "session tracking", "conversation context", "multi-turn sessions", or "user journey tracking".