Vital Lens

Documentation

A complete guide to Core Web Vitals, every tab and feature, what the scores mean, and how to act on the data.

📦 Version 1.0.0 🌐 Chrome Extension ⚡ Manifest V3

📊 Overview

Vital Lens measures real Core Web Vitals from inside your browser as pages load — no external APIs, no Lighthouse, no guesswork.

Open the extension on any page and it immediately starts collecting LCP, CLS, INP, FCP, and TTFB using the same web-vitals library that Google itself uses for the Chrome User Experience Report. Metrics update live as the page loads and as you interact with it.

Live Core Web Vitals
LCP, CLS, INP, FCP, and TTFB measured in real time from the actual page load.
📈
History & Sparkline
Every page load is saved. View per-domain history with an LCP trend chart.
🔍
LCP Element Inspector
Highlights the exact element responsible for LCP directly on the page with a CSS selector.
📌
Baseline Compare
Pin any snapshot as a baseline and track before/after deltas for all 5 metrics.
🗂️
Resource Breakdown
See every resource loaded by the page — size, duration, type — sorted by weight.
🪟
Native Sidebar Mode
Always-on Chrome Side Panel that updates as you navigate between tabs and pages.

⚙️ Installation

Vital Lens installs as a standard Chrome extension and works immediately — no account, no API key, no configuration required.

1
Install from the Chrome Web Store

Search for “Vital Lens” in the Chrome Web Store and click Add to Chrome.

2
Pin the extension

Click the puzzle-piece icon in Chrome’s toolbar, find Vital Lens, and click the pin icon to keep it accessible in one click.

3
Open it on any webpage

Navigate to any http:// or https:// page and click the Vital Lens icon. Measurement starts immediately — no reload required.

4
(Optional) Enable Sidebar Mode

Click the panel toggle icon in the popup header to dock Vital Lens as a persistent sidebar that stays open across all tabs.

⚠️
Browser-internal pages are not measurable. Vital Lens cannot run on chrome:// pages, the Chrome Web Store, or PDF viewer pages. On those pages you’ll see a “Cannot measure this page” message.

How It Works

Vital Lens uses two coordinated content scripts to measure real performance events from inside the page’s own JavaScript context.

Chrome extensions run in an isolated JavaScript context by default — separate from the page. But accurate performance measurement (LCP paint events, layout shift records, interaction timings) requires access to the same JavaScript heap that the page uses. Vital Lens solves this with a two-script architecture:

content-main.ts · MAIN world
Full JS heap access · web-vitals observers · LCP highlight
window.postMessage
content-bridge.ts · ISOLATED world
chrome.* APIs · forwards metrics to background
chrome.runtime.sendMessage
background.ts · Service Worker
merges vitals · persists snapshots · rebroadcasts
chrome.runtime.sendMessage
popup.ts / sidepanel.ts
renders live metrics in the UI

Both scripts are injected at document_start — before the page begins loading — so no paint or layout event is ever missed.

Measurement Priority

To avoid competing with page scripts, observers are set up using scheduler.postTask({ priority: 'user-blocking' }) — the highest available browser task priority. This means Vital Lens registers its observers before most page scripts run, without slowing down the page itself.

When Snapshots Are Saved

A snapshot is written to history when you navigate away from a page or close the tab — not while you’re on it. This ensures the snapshot captures the most complete set of metrics available. INP in particular can only be recorded after a user interaction, so waiting until navigate-away gives it the best chance of capturing a real value.

💡
Metrics update live while you’re on the page. What you see in the popup/sidebar is always the most current value. The saved snapshot in History reflects the final state when you left the page.

📊 Overview Tab

Your real-time performance dashboard — a weighted score ring, three Core Web Vital cards with live elapsed timers, secondary metrics, and quick-action buttons.

Performance Score Ring

The circular ring shows a weighted score from 0 to 100 calculated across all five metrics. Weights reflect each metric’s relative importance to user experience:

MetricWeightWhy
LCP35%Directly reflects perceived load speed — the most impactful CWV for users
INP30%Measures responsiveness to all interactions — replaced FID as a Core Web Vital in 2024
CLS25%Visual stability — unexpected layout shifts are one of the most frustrating user experiences
FCP5%Diagnostic — fast FCP with slow LCP indicates render-blocking resources
TTFB5%Diagnostic — high TTFB points to server, CDN, or network latency issues

Each metric that hasn’t arrived yet is scored as “needs improvement” (60/100 points for that weight), not zero. This prevents a page mid-load from showing an artificially low score.

85–100Excellent — all Core Web Vitals healthy
65–84Good — solid but some metrics need attention
45–64Needs Work — real performance gaps affecting users
0–44Poor — critical issues that will hurt rankings and users

Core Web Vital Cards (LCP · CLS · INP)

Three cards show the three official Google Core Web Vitals. Each card has a color-coded top border, the metric value, a progress bar showing how close to the “poor” threshold you are, and a rating label:

LCP
1.84s
Good
CLS
0.14
Needs Work
INP
620ms
Poor

Elapsed Timer

While a metric is still being collected, the card shows a live elapsed timer (e.g. 2.3s…) with a pulsing progress bar. This tells you exactly how long the page has been loading and when each metric typically arrives:

LCP — arrives after the largest image or text block renders (usually 1–4s)
CLS — accumulates over the session, typically finalises at 5s
INP — requires a user interaction (see below)

INP — “Tap Page to Measure”

INP (Interaction to Next Paint) measures how quickly the page responds after you click, tap, or press a key. By definition, it cannot be measured until you interact with the page — the browser has nothing to time. Vital Lens shows a grid icon with “Tap page to measure” instead of a spinner. Click, tap, or type anywhere on the page and INP will populate within a second.

ℹ️
Why doesn’t INP show on every page? If you open Vital Lens, read the metrics, and close it without clicking anything on the page, INP will never appear. This is correct browser behaviour — not a bug. Google’s field data (CrUX) only includes INP from sessions with real interactions.

Secondary Metrics (FCP · TTFB)

FCP (First Contentful Paint) and TTFB (Time to First Byte) are diagnostic metrics — not Core Web Vitals, but essential for understanding why LCP may be slow:

MetricWhat it measuresIf it’s high, suspect…
FCPHow fast the first text or image appears on screenRender-blocking CSS/JS, large critical resources, slow server
TTFBHow long until the browser receives the first byte from the serverSlow server processing, missing CDN, poor hosting, no caching

LCP Element Inspector

Click Inspect LCP to highlight the exact element that Chrome identified as the Largest Contentful Paint element — the image, heading, or text block that dominated the viewport when LCP fired. An indigo overlay appears on the page with a label showing the element type.

The panel below the buttons shows the element’s CSS selector and image source URL (if it’s an image). This pinpoints exactly what to optimise — whether it’s an uncompressed hero image, a late-loading webfont, or a large text block.

💡
The LCP element highlight auto-removes after 5 seconds. Click Inspect LCP again to re-highlight. The highlight is non-interactive — you can still click, scroll, and use the page normally while it’s visible.

Pin Baseline

Click Pin Baseline to save the current metrics snapshot as a named reference point for this URL. The baseline is stored with a timestamp and available in the Compare tab. Use this before making performance optimisations — after changes, the Compare tab will show exact before/after deltas.

PageSpeed Button

Opens Google PageSpeed Insights with the current page URL pre-filled. PageSpeed Insights runs a Lighthouse audit on a simulated throttled connection and provides server-side optimisation suggestions that Vital Lens (which runs in your browser) cannot — like image format recommendations, unused CSS elimination, and server response time analysis.


📈 History Tab

A per-domain record of every page load — with an LCP sparkline chart showing your trend over time.

LCP Trend Sparkline

When at least two history entries exist for the current domain, a sparkline chart appears showing LCP values across recent page loads (chronological, oldest to newest). Two threshold lines are drawn:

Green dashed line — LCP Good threshold (2.5s)
Red dashed line — LCP Poor threshold (4.0s)

A filled dot marks the most recent measurement. If the line trends upward over time, LCP is getting worse. If it trends down and stays below the green line, you’re in good shape.

Measurements List

Each row in the history list shows:

ColumnMeaning
URLThe page path (truncated) — hover to see the full URL
LCPColour-coded green/amber/red per the Good/Poor thresholds
CLSColour-coded — 0.000 means no layout shift was recorded
INPColour-coded — “—” means no interaction occurred during that session
TimeRelative time since the snapshot was saved (e.g. “3m ago”, “2h ago”)

Up to 40 entries are shown. History is stored per hostname — visiting any page on example.com adds to that domain’s history regardless of the path.

Data Retention

Old entries are automatically pruned based on the retention setting in the Settings tab. The default is 30 days. To keep all history indefinitely, set retention to “Forever” in Settings.


📌 Compare Tab

A before/after comparison between a pinned baseline snapshot and the current live metrics — showing exact deltas for all five metrics.

How Baselines Work

Baselines are saved per URL (exact match — not per hostname). Pin a baseline from the Overview tab’s Pin Baseline button before making performance changes, then return after deploying to see exactly what improved and by how much.

The Compare tab shows two columns:

Baseline — the pinned snapshot values
Current — live values from this session

Delta Badges

Each Current value has a delta badge showing the percentage change from baseline:

▼ −18% Better — metric improved (lower is better for LCP, CLS, INP)
▲ +24% Worse — metric regressed
± 0% Same — within ±2% change
💡
For accurate before/after testing, take the baseline on the same network and device conditions you’ll use for the “after” measurement. WiFi vs 4G vs throttled connections produce very different LCP values — comparing across different network conditions won’t be meaningful.

Clear Baseline

Click Clear Baseline to remove the pinned baseline for the current URL. The tab will show “No baseline pinned” until you pin a new one from the Overview tab.


🗂️ Resources Tab

A filterable breakdown of every network resource loaded by the page — sorted by transfer size, with type badges and load duration.

Filter Bar

FilterShows
AllEvery resource sorted by size (largest first)
JSJavaScript files — script type resources
CSSStylesheets — stylesheet type resources
ImagesImage files — image type resources
FontsWeb fonts — font type resources
FetchAPI calls — fetch / xmlhttprequest resources

Resource Row Columns

ColumnMeaning
FilenameThe resource filename (path stripped, query string stripped). Full URL visible on hover via title attribute.
SizeCompressed transfer size in bytes/KB/MB. This is what was actually sent over the wire, not the uncompressed size.
BarVisual width proportional to the largest resource in the current filter. Makes it easy to spot outliers at a glance.
Type badgeColour-coded resource type: violet = JS, green = CSS, amber = Image, pink = Font, cyan = Fetch, grey = Other.
DurationHow many milliseconds the request took from start to end. High durations on critical JS files directly delay LCP.

Up to 40 resources are shown per filter. Resources are collected from performance.getEntriesByType('resource') at the time you open the extension — they reflect the page’s load state at that moment.

💡
Start with the JS filter. Large JavaScript files are the most common cause of poor LCP and INP. Look for files over 200KB — these are candidates for code splitting, lazy loading, or removal. The duration column reveals which scripts are slow to download regardless of size (CDN latency, no compression, etc.).

⚙️ Settings Tab

Configure performance budget alerts, notification behaviour, data retention, and history management.

Performance Budget Alerts

When enabled, Vital Lens monitors every page load and fires a browser notification if any metric exceeds its budget. Budgets are checked as metrics arrive — if LCP crosses your threshold at 3.8 seconds, you’ll know immediately without having to open the extension.

Budget fieldUnitDefaultRecommended starting point
LCPmilliseconds2500msMatch the Good threshold: 2500ms
CLS× 0.01 (integer)10 (= 0.10)10 for Good threshold (0.10)
INPmilliseconds200msMatch the Good threshold: 200ms
FCPmilliseconds1800msMatch the Good threshold: 1800ms
⚠️
CLS is stored as an integer multiplied by 100. Enter 10 for a budget of 0.10, 25 for 0.25. The field label shows “×0.01” as a reminder. Vital Lens divides by 100 internally when comparing against the actual CLS float value.

Browser Notifications

When enabled alongside Budget Alerts, Vital Lens shows a native Chrome notification when any budget is exceeded. The notification includes the metric name, its actual value, and your configured budget — so you can see at a glance how far over it went. Notification ID is unique per tab per alert so multiple violations are shown separately.

Data Retention

Controls how long history snapshots are kept in your browser’s IndexedDB. Options: 7 days, 30 days (default), 90 days, or Forever. Old snapshots are pruned automatically after each new page load based on this setting.

Clear History

The Clear All History for This Domain button in the Danger Zone permanently deletes all saved snapshots for the current hostname. This cannot be undone. Baselines for that domain are not affected — those are stored separately and must be cleared from the Compare tab.



📐 Interpreting Scores

Quick reference for all five metrics — what counts as Good, Needs Work, and Poor, and what each threshold means in practice.

Core Web Vitals Thresholds

MetricGoodNeeds WorkPoorWhat it means
LCP
Largest Contentful Paint
≤ 2.5s 2.5s – 4.0s > 4.0s How fast the main content of the page loads and becomes visible
CLS
Cumulative Layout Shift
≤ 0.10 0.10 – 0.25 > 0.25 How much page content unexpectedly moves after it first appears
INP
Interaction to Next Paint
≤ 200ms 200ms – 500ms > 500ms How quickly the page responds visually after every user interaction
FCP
First Contentful Paint
≤ 1.8s 1.8s – 3.0s > 3.0s How fast the first text or image appears — signals render-blocking issues
TTFB
Time to First Byte
≤ 800ms 800ms – 1.8s > 1.8s How fast the server (or CDN) responds — the foundation every other metric builds on

Performance Score Ranges

ScoreGradeTypical meaning
85–100ExcellentAll three Core Web Vitals are Good. Page likely passes Google’s CWV assessment.
65–84GoodMost metrics are Good, one may be in Needs Work. Investigate and address the outlier.
45–64Needs WorkMultiple metrics are struggling. Users will notice slowness or jank. Prioritise LCP and INP fixes.
0–44PoorCritical issues. High likelihood of failing Google’s Core Web Vitals assessment, which affects Search ranking.
ℹ️
Vital Lens measures your personal browser session. Google’s rankings use field data (real user measurements from Chrome across many visits). Vital Lens data closely correlates with field data when measured on a typical network, but a single measurement on a fast developer machine may look better than what real users on mobile experience. Use PageSpeed Insights to see actual field data for your URL.

Frequently Asked Questions

Common questions about measurements, data accuracy, browser behaviour, and troubleshooting.

Measurements & Accuracy

My LCP looks good in Vital Lens but PageSpeed Insights says it’s poor. Why?

Vital Lens measures your specific browser session on your current network. PageSpeed Insights simulates a mid-range mobile device on a throttled 4G connection, which is significantly slower than most developer setups. Additionally, PageSpeed Insights uses field data (real user measurements collected by Chrome from many visitors), which may include users on slow connections or low-end devices.

Both measurements are useful: Vital Lens shows you real-time feedback while you make changes; PageSpeed Insights shows how real users across all conditions are experiencing your page.

LCP keeps changing every time I reload. Is that normal?

Yes, some variation is completely normal. LCP can vary based on: browser cache state (first load vs cached load), network congestion, server response time variation, third-party script load times, and CPU load on your machine. Measure across 3–5 reloads on an empty cache (Ctrl+Shift+R / Cmd+Shift+R) to get a representative value. The History tab’s sparkline makes this easy to spot trends.

CLS is showing 0.000 on a page that I know has layout shifts. Why?

CLS is cumulative — it only accumulates during your current session. If the layout shifts happen as the page loads but you opened Vital Lens after they occurred, the current value will be 0. To capture the full CLS: reload the page fresh, then open Vital Lens before interacting with the page. The CLS value will accumulate as the page settles. Also note: layout shifts caused by user interactions (like clicking an accordion) are excluded from CLS by the spec — only unexpected shifts count.

INP stays blank even after I click on the page. What’s wrong?

A few possibilities: (1) The click target might not be an interactive element that triggers a visible repaint — INP requires an interaction that causes the browser to update what’s displayed on screen. Clicking a static element with no visual feedback won’t register. Try clicking a button, link, or dropdown. (2) Some pages use passive event listeners that don’t trigger a repaint on interaction. (3) On very fast pages, INP may register as 0ms or <10ms and appear as “0ms · Good” rather than staying blank — look more carefully at the card.

History & Data

No history is showing for a domain I’ve visited many times. Why?

Snapshots are saved when you navigate away from a page or close the tab — not while you’re on the page. If you visited a page and closed the browser directly, the snapshot may not have saved in time (the service worker may have been killed). Also, a snapshot is only saved if at least one core metric (LCP or CLS) was measured — pages where the extension had no time to collect any data won’t generate history entries.

History disappeared after I cleared Chrome data. Can I recover it?

No — Vital Lens stores data in IndexedDB which is cleared when you clear site data or browser storage. If you used Chrome’s “Clear browsing data” with “Hosted app data” or “Cookies and other site data” checked with a broad time range, extension storage may have been included. Uninstalling and reinstalling the extension also clears all history. There is no cloud backup — data is local only.

Can I export my history data?

Export functionality is not available in version 1.0. History can be viewed within the extension’s History tab, and the Compare tab allows before/after comparison against a pinned baseline. CSV/JSON export is planned for a future version.

Sidebar & UI

I enabled Sidebar Mode but it doesn’t open automatically when I switch tabs.

This is a Chrome API limitation: chrome.sidePanel.open() requires a user gesture. When you switch tabs, the extension attempts to open the panel but Chrome may silently reject the request because there’s no direct user gesture on the extension. The panel remains available via the side panel button in Chrome’s toolbar (the small panel icon to the right of the address bar). Once you manually open it once after a tab switch, it stays open for subsequent switches during that session.

The sidebar URL header shows “—” and metrics aren’t loading.

This usually means the sidebar opened before it could identify which tab is active. Try clicking anywhere in the sidebar to trigger a re-check, or close and reopen the sidebar. If the issue persists, hard-reload the active tab (Cmd+Shift+R / Ctrl+Shift+R) to ensure the content scripts are freshly injected.

The extension shows an error or no data on a specific site.

Some sites use a strict Content Security Policy (CSP) that blocks script injection. In these cases, the content scripts cannot run and Vital Lens cannot collect measurements. This is particularly common on high-security pages like banking portals and internal enterprise apps. chrome:// pages, the Chrome Web Store, and PDF viewer tabs also block all extensions by design.

Performance Budgets

I’m getting budget alert notifications on every single page load. How do I reduce them?

Either raise your budget thresholds to be less strict, or disable Browser Notifications in Settings (keeping Budget Alerts enabled means budgets are still tracked but without desktop notifications). You can also disable Budget Alerts entirely — metrics will still be measured and colour-coded, you just won’t get notifications. The Settings tab lets you tune each metric’s budget independently, so you can be strict about LCP but lenient about FCP.

I set a CLS budget of 0.10 but the field shows “10”. Is that right?

Yes — CLS budgets are stored as integers multiplied by 100 to avoid floating-point precision issues in number inputs. Enter 10 for a budget of 0.10, 25 for 0.25. The field is labelled “×0.01” to indicate this. Vital Lens divides the stored value by 100 internally when checking whether your actual CLS (e.g. 0.08) exceeds the budget.

Privacy & Data

Does Vital Lens track which sites I visit?

No. There is no analytics, telemetry, or usage tracking of any kind. Vital Lens does not know what sites you’ve visited. All data — history, baselines, settings — is stored exclusively in your browser’s local IndexedDB. Nothing is transmitted to echozat.com or any external service. The extension makes no outbound network calls.

What permissions does Vital Lens request and why?

storage — to save history, baselines, and settings in IndexedDB. tabs — to query the active tab’s URL and listen for tab switches and navigations. activeTab — to inject content scripts into the current page for measurement. scripting — required by MV3 for content script injection. sidePanel — to enable the Native Side Panel feature. notifications — to show budget alert notifications. host permissions (<all_urls>) — content scripts must be allowed to run on any page to measure any site you visit.