Core Web Vitals Audit Spreadsheet: Track, Score & Prioritize Fixes
Download a Core Web Vitals audit spreadsheet to track LCP, INP, and CLS scores. Auto-prioritize fixes by impact vs effort with built-in scoring formulas.
A development team ships a CLS fix for their product listing page. Two sprints later, a designer adds a dynamically loaded promotional banner without explicit dimensions. The CLS score regresses from 0.04 to 0.31 — but nobody notices because there's no tracking system in place. The regression lives in production for six weeks until a quarterly Lighthouse audit finally surfaces it, after Google has already demoted the page from position 3 to position 11.
This failure pattern — fix, regress, rediscover — happens because most teams audit Core Web Vitals reactively rather than tracking them systematically. A structured Core Web Vitals audit spreadsheet transforms CWV management from ad-hoc Lighthouse runs into a scored, prioritized, and time-series tracked engineering workflow.
This guide walks through building a production-grade CWV tracking spreadsheet with auto-prioritization formulas, provides exact column schemas and scoring logic, and shows how to pipe field data from the Chrome UX Report (CrUX) API directly into your tracking system.
Why Spreadsheet-Based CWV Tracking Beats Ad-Hoc Lighthouse Runs
Running Lighthouse on individual pages produces lab data snapshots that suffer from three structural limitations:
| Limitation | Impact |
|---|---|
| No historical tracking | You see today's score but can't tell if it improved or regressed from last week |
| No cross-page visibility | A 50-page site requires 50 separate Lighthouse runs — nobody does this consistently |
| No prioritization | Lighthouse reports every issue equally — a 0.01 CLS shift gets the same attention as a 0.45 CLS violation |
| Lab vs field gap | Lighthouse simulates a single device profile; real users on varied hardware produce different scores |
A structured spreadsheet solves all four problems by establishing a single source of truth where every page's CWV scores, historical trends, responsible owners, and fix priorities live in one sortable, filterable view.
The Core Spreadsheet Architecture: 4 Tabs
Tab 1: Page Inventory & Current Scores
This is the master registry. Every page that matters for organic traffic gets a row.
| Column | Data Type | Source | Example |
|---|---|---|---|
| Page URL | URL | Sitemap / crawl | /products/wireless-headphones |
| Page Type | Enum | Manual | PDP, PLP, Blog, Landing |
| Monthly Sessions | Integer | GA4 / Search Console | 12,450 |
| LCP (p75, ms) | Integer | CrUX API / field data | 2,340 |
| INP (p75, ms) | Integer | CrUX API / field data | 287 |
| CLS (p75) | Float | CrUX API / field data | 0.08 |
| LCP Status | Enum (auto) | Formula | Good / Needs Improvement / Poor |
| INP Status | Enum (auto) | Formula | Good / Needs Improvement / Poor |
| CLS Status | Enum (auto) | Formula | Good / Needs Improvement / Poor |
| Overall CWV | Pass/Fail (auto) | Formula | PASS (all three green) or FAIL |
| Owner | Text | Manual | @frontend-team |
| Last Audited | Date | Manual / script | 2026-09-15 |
Status Classification Formulas
// LCP Status (cell F2 contains LCP in ms)
=IF(F2<=2500, "Good", IF(F2<=4000, "Needs Improvement", "Poor"))
// INP Status (cell G2 contains INP in ms)
=IF(G2<=200, "Good", IF(G2<=500, "Needs Improvement", "Poor"))
// CLS Status (cell H2 contains CLS score)
=IF(H2<=0.1, "Good", IF(H2<=0.25, "Needs Improvement", "Poor"))
// Overall CWV Pass/Fail
=IF(AND(I2="Good", J2="Good", K2="Good"), "PASS", "FAIL")Tab 2: Issue Registry & Prioritization Matrix
Every detected CWV issue gets its own row with a calculated priority score.
| Column | Data Type | Example |
|---|---|---|
| Issue ID | Auto-increment | CWV-042 |
| Affected Page(s) | URL(s) | /products/* (pattern) |
| Metric | Enum | LCP / INP / CLS |
| Current Value | Number | 3,200ms |
| Target Value | Number | <2,500ms |
| Root Cause | Text | Unoptimized hero image (2.4MB PNG) |
| Fix Description | Text | Convert to AVIF, add srcset, preload |
| Impact Score (1-5) | Integer | 5 (high traffic page, severe regression) |
| Effort Score (1-5) | Integer | 2 (image optimization, low complexity) |
| Priority Score | Formula | =Impact * (6 - Effort) → 20 |
| Status | Enum | Backlog / In Progress / Deployed / Verified |
| Sprint | Text | 2026-Q3-S4 |
| Verification Date | Date | 2026-09-20 |
| Post-Fix Value | Number | 1,890ms |
The Priority Scoring Formula
The priority score combines impact severity with effort inversion — high-impact, low-effort fixes surface first:
// Priority = Impact × (6 - Effort)
// Impact 5, Effort 1 → 5 × 5 = 25 (quick wins — do first)
// Impact 5, Effort 5 → 5 × 1 = 5 (major projects — plan carefully)
// Impact 1, Effort 5 → 1 × 1 = 1 (deprioritize — low ROI)💡 Engineering Rule of Thumb: Sort the Issue Registry by Priority Score descending. The top 5 issues will almost always deliver 80% of the CWV improvement for 20% of the engineering effort. Present this sorted list in sprint planning to justify CWV work with quantified impact.
Tab 3: Historical Trend Tracking
This tab records weekly or bi-weekly CWV snapshots for regression detection.
| Column | Data Type |
|---|---|
| Snapshot Date | Date |
| Page URL | URL |
| LCP (p75) | Integer |
| INP (p75) | Integer |
| CLS (p75) | Float |
| Delta LCP | Formula: =C2-VLOOKUP(...) (vs previous snapshot) |
| Delta INP | Formula |
| Delta CLS | Formula |
| Regression Flag | Formula: =IF(OR(F2>100, G2>50, H2>0.05), "⚠️ REGRESSION", "") |
// Regression detection formula (cell I2)
// Flags if LCP worsened by >100ms, INP by >50ms, or CLS by >0.05
=IF(OR(F2>100, G2>50, H2>0.05), "⚠️ REGRESSION", "✅ Stable")This tab is the early warning system. When a deployment introduces a font loading change, a new third-party script, or a layout modification, the next snapshot immediately flags the regression — before Google's 28-day CrUX rolling average reflects it in Search Console.
Tab 4: CrUX API Data Pipeline
Automate data collection by pulling field data from the Chrome UX Report API:
# crux_to_sheets.py — Pull CrUX field data into your spreadsheet
import requests
import csv
from datetime import datetime
CRUX_API_KEY = "YOUR_API_KEY"
CRUX_ENDPOINT = "https://chromeuxreport.googleapis.com/v1/records:queryRecord"
PAGES = [
"https://example.com/",
"https://example.com/products/wireless-headphones",
"https://example.com/blog/best-headphones-2026",
]
def fetch_crux_data(url: str) -> dict:
"""Fetch p75 CWV field data for a single URL from CrUX API."""
payload = {
"url": url,
"formFactor": "PHONE", # Mobile field data
"metrics": [
"largest_contentful_paint",
"interaction_to_next_paint",
"cumulative_layout_shift"
]
}
resp = requests.post(
f"{CRUX_ENDPOINT}?key={CRUX_API_KEY}",
json=payload
)
if resp.status_code != 200:
return {"url": url, "lcp": "N/A", "inp": "N/A", "cls": "N/A"}
metrics = resp.json().get("record", {}).get("metrics", {})
return {
"url": url,
"lcp": metrics.get("largest_contentful_paint", {})
.get("percentiles", {}).get("p75", "N/A"),
"inp": metrics.get("interaction_to_next_paint", {})
.get("percentiles", {}).get("p75", "N/A"),
"cls": metrics.get("cumulative_layout_shift", {})
.get("percentiles", {}).get("p75", "N/A"),
}
# Fetch and export
rows = [fetch_crux_data(url) for url in PAGES]
filename = f"crux_snapshot_{datetime.now().strftime('%Y%m%d')}.csv"
with open(filename, "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["url", "lcp", "inp", "cls"])
writer.writeheader()
writer.writerows(rows)
print(f"Exported {len(rows)} rows to {filename}")# Run weekly via cron and import the CSV into your spreadsheet
python3 crux_to_sheets.py
# Output: crux_snapshot_20260916.csvBuilding the Impact-Effort Matrix Visualization
The most actionable view in the spreadsheet is a scatter plot of Impact (Y-axis) vs Effort (X-axis), divided into four quadrants:
| Quadrant | Impact | Effort | Action |
|---|---|---|---|
| Quick Wins (top-left) | High | Low | Do immediately — maximum ROI |
| Major Projects (top-right) | High | High | Plan into roadmap — significant effort justified by impact |
| Fill-Ins (bottom-left) | Low | Low | Batch during slack time — cheap but low value |
| Deprioritize (bottom-right) | Low | High | Skip unless mandated — poor ROI |
Common Quick Wins by Metric
LCP Quick Wins (Impact: High, Effort: Low):
├── Convert hero images from PNG/JPG to WebP/AVIF (30-60% size reduction)
├── Add width/height attributes to LCP images (eliminates CLS overlap)
├── Preload LCP image with <link rel="preload" as="image">
└── Set fetchpriority="high" on the LCP <img> element
INP Quick Wins:
├── Add loading="lazy" to below-fold images (reduces main thread work)
├── Defer non-critical third-party scripts (analytics, chat widgets)
├── Break up event handlers exceeding 50ms with requestIdleCallback
└── Replace synchronous JSON.parse on large payloads with streaming
CLS Quick Wins:
├── Add explicit width/height to all <img> and <video> elements
├── Reserve space for ad slots with min-height on container
├── Use font-display: optional or size-adjust on @font-face
└── Set contain: layout on dynamically injected content containersScoring Your Site: The Aggregate CWV Health Score
Transform raw per-page metrics into a single site-wide health score that executives can track quarterly:
// Site-Wide CWV Health Score Formula
// Weight pages by traffic (monthly sessions)
CWV_Health = Σ(page_weight × page_pass) / Σ(page_weight) × 100
Where:
page_weight = monthly_sessions for that page
page_pass = 1 if all three CWV metrics are "Good", else 0
Example:
Homepage: 15,000 sessions × 1 (PASS) = 15,000
Product page: 8,000 sessions × 0 (FAIL) = 0
Blog index: 3,000 sessions × 1 (PASS) = 3,000
Total weight: 26,000
CWV_Health = (15,000 + 0 + 3,000) / 26,000 × 100 = 69.2%// Spreadsheet formula for CWV Health Score
=SUMPRODUCT((L2:L100="PASS")*1, E2:E100) / SUM(E2:E100) * 100
// Where column L = "Overall CWV" (PASS/FAIL), column E = Monthly SessionsThis traffic-weighted score prevents a low-traffic 404 page with bad CLS from dragging down the overall score equally with your homepage. A site with 95% of its traffic on CWV-passing pages scores 95%, even if a handful of legacy pages fail.
Tracking Improvement Over Time: The Velocity Metric
Beyond the current health score, track the improvement velocity — how many CWV issues your team resolves per sprint:
// CWV Fix Velocity (per sprint)
Velocity = COUNT(issues where status changed to "Verified" in this sprint)
// Weighted Velocity (accounts for issue priority)
W_Velocity = SUM(Priority_Score of issues verified this sprint)
// Burndown: remaining issues weighted by priority
Burndown = SUM(Priority_Score of issues NOT in "Verified" status)| Sprint | Issues Fixed | Weighted Velocity | Remaining Burndown | Health Score |
|---|---|---|---|---|
| 2026-Q3-S1 | 3 | 52 | 148 | 61% |
| 2026-Q3-S2 | 5 | 78 | 70 | 74% |
| 2026-Q3-S3 | 4 | 45 | 25 | 89% |
| 2026-Q3-S4 | 2 | 25 | 0 | 96% |
This burndown view translates CWV work into sprint-compatible metrics that engineering managers can present alongside feature velocity.
How BugViso Automates CWV Data Collection at Scale
Building and maintaining a CWV audit spreadsheet manually works for sites with 10–20 key pages. For larger sites — e-commerce catalogs, multi-language portals, content-heavy publishers — manual Lighthouse runs and CrUX API calls don't scale.
BugViso's multi-page crawl engine automates the data collection layer:
- Per-page CWV capture using the official
web-vitalslibrary (LCP, CLS, INP, FCP, TTFB) injected into a headless Chromium session for every crawled page — not a Lighthouse simulation, but actual browser rendering metrics. - Site-wide aggregation reporting total pages crawled, average and worst-page LCP, and total CLS/accessibility violations across the crawl — the exact data points that populate Tab 1 of the spreadsheet.
- Network throttling simulation via the Advanced Speed & Performance Engine that re-loads under Slow 3G and Fast 3G profiles, revealing performance regressions hidden under fast office networks.
- Code coverage analysis identifying unused JavaScript and CSS bytes shipped in initial bundles — the root cause data that populates the Issue Registry (Tab 2) with specific fix targets.
- Remediation playbook that pairs each detected issue with exact copy-paste code snippets, terminal commands, and configuration changes — eliminating the "Root Cause" and "Fix Description" research that consumes most of the spreadsheet authoring time.
The audit results map directly to the spreadsheet schema: page URLs, per-page CWV scores, identified issues with severity ratings, and actionable fix instructions — exported from a single automated crawl rather than 50 manual Lighthouse runs.
Common Spreadsheet Mistakes to Avoid
Mistake 1: Tracking Lab Data Instead of Field Data
Lighthouse scores are lab measurements on a simulated Moto G Power with throttled CPU. Google's ranking algorithm uses field data from the Chrome UX Report (28-day rolling p75 from real users). Track CrUX field data in your spreadsheet, and use Lighthouse lab data only for debugging specific issues.
Mistake 2: Equal-Weighting All Pages
A 404 error page with 12 monthly visits and a 4.2s LCP should not receive the same priority as your homepage with 50,000 visits and a 2.8s LCP. Always weight pages by traffic when calculating aggregate scores and fix priorities.
Mistake 3: Not Tracking Post-Fix Verification
An issue marked "Deployed" is not "Fixed" until the field data confirms the improvement. Add a "Post-Fix Value" column and a "Verification Date" that records the CrUX p75 value 28+ days after deployment.
Mistake 4: Ignoring Cross-Metric Interactions
Fixing LCP by lazy-loading images can worsen CLS if the lazy-loaded images lack explicit dimensions. Always re-measure all three CWV metrics after deploying a fix for any single metric.
Frequently Asked Questions
How often should I update the CWV spreadsheet?
Bi-weekly matches the CrUX API's data refresh cadence and aligns with most sprint cycles. Weekly is useful during active optimization sprints. Monthly is the minimum — any less frequent and regressions go undetected for too long.
Should I track all pages or just high-traffic ones?
Track the top 20–30 pages by organic traffic plus any pages in active development. For e-commerce, include template representatives (one PDP, one PLP, one category page) rather than every individual product URL.
What's a good target CWV Health Score?
Aim for 90%+ of traffic-weighted pages passing all three CWV thresholds. Google's own data shows that sites achieving this level see measurable ranking improvements in competitive verticals. A score below 70% indicates systemic performance issues requiring architectural intervention.
Can I automate the entire spreadsheet?
Yes. Combine the CrUX API script (Tab 4) with Google Sheets API or a Python gspread integration to auto-populate scores bi-weekly. For issue detection and fix suggestions, pipe the output of an automated site-wide crawl into Tab 2. A free BugViso audit provides the per-page CWV data and remediation playbooks that populate both the Page Inventory and Issue Registry tabs.
How do I present CWV progress to non-technical stakeholders?
Use the traffic-weighted Health Score (single percentage) and the burndown chart. These translate CWV work into business-language KPIs: "We improved from 61% to 96% site health this quarter, reducing bounce rate on our highest-traffic pages by an estimated 12%."
See where your site stands
Run a free BugViso audit for SEO, speed, accessibility and AI search readiness — with fixes you can ship today.