Macklin Hartley
https://macklin.me
⬛ User-Centric Performance
⬛ Capture Metrics from End User Devices
⬛ Continuous Monitoring
❌ Web Performance Improvement
const start = Date.now();
window.addEventListener('load', () => {
console.log({ load: Date.now() - start });
});
https://www.w3.org/TR/navigation-timing-2/
window.performance.timing
Adobe
Airbnb Inc
Akamai Technologies
Alibaba Group
Apple, Inc.
Baidu, Inc.
BBC
Cloudflare
Fastly
Google LLC
Microsoft Corporation
Mozilla Foundation
Salesforce
Shopify
The New York Times
Wikimedia Foundation
https://web.dev/user-centric-performance-metrics/
https://web.dev/user-centric-performance-metrics/
TTFB
Time to First Byte
FCP
First Contentful Paint
LCP
Last Contentful Paint
FID
First Input Delay
INP
Interaction to Next Paint
CLS
Cumulative Layout Shift
https://developer.mozilla.org/en-US/docs/Glossary/time_to_first_byte
the time between the browser requesting a page and when it receives the first byte of information from the server.
TTFB
Time to First Byte
https://www.mindspun.com/blog/time-to-first-byte-ttfb/
TTFB
Time to First Byte
GOOD
POOR
NEEDS
IMPROVEMENT
800ms
1.8s
https://web.dev/ttfb/
TTFB
Time to First Byte
https://web.dev/fcp/
the time from when the page starts loading to when any part of the page's content is rendered on the screen...
FCP
First Contentful Paint
https://macklin.me/7-useful-tools-for-monitoring-web-performance-user-experience
FCP
First Contentful Paint
GOOD
POOR
NEEDS
IMPROVEMENT
1.8 sec
3.0 sec
https://web.dev/fcp/
FCP
First Contentful Paint
https://calibreapp.com/blog/largest-contentful-paint
...tracks how many seconds it takes for your page’s most data-intensive, above-the-fold element to load.
LCP
Largest Contentful Paint
https://macklin.me/7-useful-tools-for-monitoring-web-performance-user-experience
LCP
Largest Contentful Paint
GOOD
POOR
NEEDS
IMPROVEMENT
2.5 sec
4.0 sec
https://web.dev/lcp/
LCP
Largest Contentful Paint
https://web.dev/fid/
the time from when a user first interacts with a page[...] to the time when the browser is actually able to begin processing event handlers...
FID
First Input Delay
https://requestmetrics.com/web-performance/first-input-delay
FID
First Input Delay
GOOD
POOR
NEEDS
IMPROVEMENT
100ms
300ms
https://web.dev/fid/
FID
First Input Delay
https://web.dev/inp/
...represents a page's overall responsiveness by measuring all click, tap, and keyboard interactions made with a page.
INP
Interaction to Next Paint
https://web.dev/inp/
INP
Interaction to Next Paint
GOOD
POOR
NEEDS
IMPROVEMENT
200ms
500ms
https://web.dev/inp/
INP
Interaction to Next Paint
https://web.dev/cls/
[a calculated score] for every unexpected layout shift that occurs during the entire lifespan of a page...
CLS
Cumulative Layout Shift
https://macklin.me/7-useful-tools-for-monitoring-web-performance-user-experience
CLS
Cumulative Layout Shift
CLS
Cumulative Layout Shift
GOOD
POOR
NEEDS
IMPROVEMENT
0.1
0.25
https://web.dev/cls/
CLS
Cumulative Layout Shift
https://developers.google.com/search/blog/2020/11/timing-for-page-experience
Interactivity
Interaction to Next Paint (INP)
Loading
Largest Contentful Paint (LCP)
Visual Stability
Cumulative Layout Shift (CLS)
Mobile Friendly
HTTPS
No Intrusive Interstitials
Search signals
for web page
experience
Core
Web
Vitals
TTFB ≈ 290ms
FCP ≈ 500ms
LCP ≈ 1.625 sec
https://csswizardry.com/2022/08/measure-what-you-impact-not-what-you-influence/
https://web.dev/vodafone/
✅ User-Centric Performance
⬛ Capture Metrics from End User Devices
⬛ Continuous Monitoring
Ad-hoc Testing
Synthetic Monitoring
Real User Monitoring (RUM)
Lighthouse for
Google Chrome
web.dev/measure
👍 Free, no setup required
👎 Once-off, manual tests only
👎 Unlikely to represent real world traffic
Calibre
https://calibreapp.com/features/pull-request-reviews
👍 CI/CD + scheduled automation
👍 Monitor change over time
👎 Not free, although competitively priced
👎 Still not representative of real world traffic
https://engineering.linecorp.com/en/blog/the-baseline-for-web-development-in-2022
https://engineering.linecorp.com/en/blog/the-baseline-for-web-development-in-2022
Low End Device
Motorola Moto E13
10x
High End Device
Apple iPhone 15 Pro
# of devices
performance
50th percentile
Apple iPhone 11 (2019)
Samsung Galaxy S20 Ultra (2021)
# of devices
performance
75th percentile
Motorola Moto E13 (2023)
Google Pixel 4a (2020)
Apple iPhone 6 (2015)
Samsung Galaxy S6 (2015)
# of devices
performance
25% of people receive
a poorer experience
https://engineering.linecorp.com/en/blog/the-baseline-for-web-development-in-2022
86.8%
https://www.speedtest.net/global-index/united-states
3G
4G
~1 second
~2.3 seconds
4 second budget
5G
less than 1 second
46.26 Mb/s
Reno, NV
181.74 Mb/s
Glendale, AZ
390%
https://www.speedtest.net/global-index
https://www.speedtest.net/global-index
101 ms
Anchorage, AK
39 ms
Plano, TX
35%
Alex Russel - Progressive Performance (Chrome Dev Summit 2016)
https://www.youtube.com/watch?v=4bZvq3nodf4
Capturing performance metrics directly from devices in the real world to observe differences in functionality, reliability, and responsiveness.
https://developers.google.com/web/tools/chrome-user-experience-report
👍 Free, publicly available data
👎 Frustrating setup process
👎 Delayed, monthly aggregation
👎 Google Chrome devices only
https://calibreapp.com/tools/core-web-vitals-checker
https://calibreapp.com/tools/core-web-vitals-checker
👍 On-demand access
👍 Zero configuration
👎 Same upsides and downsides as CrUX Report
Sponsor | TTFB | FCP | LCP ↑ | FID | INP | CLS |
---|---|---|---|---|---|---|
🥇 Liberty | 🟢 542ms | 🟢 1.18s | 🟢 1.53s | 🟢 14ms | 🟢 180ms | 🟡 0.14 |
🥈 Umbraco | 🟢 450ms | 🟢 948ms | 🟢 1.58s | 🟢 5ms | 🟢 111ms | 🟢 0.02 |
🥉 Github | 🟡 955ms | 🟢 1.37s | 🟢 1.69s | 🟢 6ms | 🟢 98ms | 🟢 0.06 |
Luminary | 🟢 789ms | 🟢 1.29s | 🟢 1.84s | 🟢 8ms | 🟢 82ms | 🟢 0.03 |
Webjet | 🟢 440ms | 🟢 1.26s | 🟢 1.86s | 🟢 23ms | 🟡 459ms | 🟢 0.08 |
Qoria | 🟢 579ms | 🟢 1.48s | 🟢 1.88s | 🟢 5ms | 🟢 44ms | 🟢 0.00 |
Kodez | 🟡 1.15s | 🟢 1.69s | 🟢 1.96s | ⚪ | 🟢 49ms | 🟢 0.01 |
DevExpress | 🟢 686ms | 🟢 1.62s | 🟢 2.15s | 🟢 3ms | 🟢 47ms | 🟢 0.00 |
Octopus Deploy | 🟡 1.11s | 🟡 2.43s | 🟡 2.5s | 🟢 5ms | 🟢 62ms | 🟢 0.03 |
SSW | 🟡 1.4s | 🟡 2.32s | 🟡 2.77s | 🟢 10ms | 🟢 78ms | 🟢 0.02 |
DDD Melbourne | 🔴 2.35s | 🟡 2.51s | 🟡 2.94s | 🟢 8ms | 🟢 79ms | 🟢 0.01 |
Kaleida | 🟡 1.56s | 🔴 3.35s | 🟡 3.53s | ⚪ | ⚪ | 🟢 0.00 |
Netwealth | 🔴 2.63s | 🔴 3.81s | 🔴 4.15s | 🟢 2ms | 🟢 55ms | 🟢 0.01 |
Insight | 🟡 1.6s | 🔴 5.18s | 🔴 5.71s | 🟢 8ms | 🟢 128ms | 🟡 0.21 |
JustDigitalPeople | ⚪ | ⚪ | ⚪ | ⚪ | ⚪ | ⚪ |
The Sizzle | ⚪ | ⚪ | ⚪ | ⚪ | ⚪ | ⚪ |
Sponsor | TTFB | FCP | LCP ↑ | FID | INP | CLS |
---|---|---|---|---|---|---|
🥇 Liberty | 🟢 542ms | 🟢 1.18s | 🟢 1.53s | 🟢 14ms | 🟢 180ms | 🟡 0.14 |
🥈 Umbraco | 🟢 450ms | 🟢 948ms | 🟢 1.58s | 🟢 5ms | 🟢 111ms | 🟢 0.02 |
🥉 Github | 🟡 955ms | 🟢 1.37s | 🟢 1.69s | 🟢 6ms | 🟢 98ms | 🟢 0.06 |
Luminary | 🟢 789ms | 🟢 1.29s | 🟢 1.84s | 🟢 8ms | 🟢 82ms | 🟢 0.03 |
Webjet | 🟢 440ms | 🟢 1.26s | 🟢 1.86s | 🟢 23ms | 🟡 459ms | 🟢 0.08 |
Qoria | 🟢 579ms | 🟢 1.48s | 🟢 1.88s | 🟢 5ms | 🟢 44ms | 🟢 0.00 |
Kodez | 🟡 1.15s | 🟢 1.69s | 🟢 1.96s | ⚪ | 🟢 49ms | 🟢 0.01 |
DevExpress | 🟢 686ms | 🟢 1.62s | 🟢 2.15s | 🟢 3ms | 🟢 47ms | 🟢 0.00 |
Octopus Deploy | 🟡 1.11s | 🟡 2.43s | 🟡 2.5s | 🟢 5ms | 🟢 62ms | 🟢 0.03 |
SSW | 🟡 1.4s | 🟡 2.32s | 🟡 2.77s | 🟢 10ms | 🟢 78ms | 🟢 0.02 |
DDD Melbourne | 🔴 2.35s | 🟡 2.51s | 🟡 2.94s | 🟢 8ms | 🟢 79ms | 🟢 0.01 |
Kaleida | 🟡 1.56s | 🔴 3.35s | 🟡 3.53s | ⚪ | ⚪ | 🟢 0.00 |
Netwealth | 🔴 2.63s | 🔴 3.81s | 🔴 4.15s | 🟢 2ms | 🟢 55ms | 🟢 0.01 |
Insight | 🟡 1.6s | 🔴 5.18s | 🔴 5.71s | 🟢 8ms | 🟢 128ms | 🟡 0.21 |
JustDigitalPeople | ⚪ | ⚪ | ⚪ | ⚪ | ⚪ | ⚪ |
The Sizzle | ⚪ | ⚪ | ⚪ | ⚪ | ⚪ | ⚪ |
Sponsor | TTFB | FCP | LCP ↑ | FID | INP | CLS |
---|---|---|---|---|---|---|
🥇 Liberty | 🟢 542ms | 🟢 1.18s | 🟢 1.53s | 🟢 14ms | 🟢 180ms | 🟡 0.14 |
🥈 Umbraco | 🟢 450ms | 🟢 948ms | 🟢 1.58s | 🟢 5ms | 🟢 111ms | 🟢 0.02 |
🥉 Github | 🟡 955ms | 🟢 1.37s | 🟢 1.69s | 🟢 6ms | 🟢 98ms | 🟢 0.06 |
Luminary | 🟢 789ms | 🟢 1.29s | 🟢 1.84s | 🟢 8ms | 🟢 82ms | 🟢 0.03 |
Webjet | 🟢 440ms | 🟢 1.26s | 🟢 1.86s | 🟢 23ms | 🟡 459ms | 🟢 0.08 |
Qoria | 🟢 579ms | 🟢 1.48s | 🟢 1.88s | 🟢 5ms | 🟢 44ms | 🟢 0.00 |
Kodez | 🟡 1.15s | 🟢 1.69s | 🟢 1.96s | ⚪ | 🟢 49ms | 🟢 0.01 |
DevExpress | 🟢 686ms | 🟢 1.62s | 🟢 2.15s | 🟢 3ms | 🟢 47ms | 🟢 0.00 |
Octopus Deploy | 🟡 1.11s | 🟡 2.43s | 🟡 2.5s | 🟢 5ms | 🟢 62ms | 🟢 0.03 |
SSW | 🟡 1.4s | 🟡 2.32s | 🟡 2.77s | 🟢 10ms | 🟢 78ms | 🟢 0.02 |
DDD Melbourne | 🔴 2.35s | 🟡 2.51s | 🟡 2.94s | 🟢 8ms | 🟢 79ms | 🟢 0.01 |
Kaleida | 🟡 1.56s | 🔴 3.35s | 🟡 3.53s | ⚪ | ⚪ | 🟢 0.00 |
Netwealth | 🔴 2.63s | 🔴 3.81s | 🔴 4.15s | 🟢 2ms | 🟢 55ms | 🟢 0.01 |
Insight | 🟡 1.6s | 🔴 5.18s | 🔴 5.71s | 🟢 8ms | 🟢 128ms | 🟡 0.21 |
JustDigitalPeople | ⚪ | ⚪ | ⚪ | ⚪ | ⚪ | ⚪ |
The Sizzle | ⚪ | ⚪ | ⚪ | ⚪ | ⚪ | ⚪ |
✅ User-Centric Performance
✅ Capture Metrics from End User Devices
⬛ Continuous Monitoring
User Centric Performance | Capture Metrics from Real Users | Continuous Monitoring | |
---|---|---|---|
window.onload() | ⬛ | ⬛ | ⬛ |
Google Lighthouse | ✅ | ⬛ | ⬛ |
Calibre | ✅ | ⬛ | ⬛ |
CrUX Report | ✅ | ✅ | ⬛ |
Web Vitals Checker | ✅ | ✅ | ⬛ |
??? | ✅ | ✅ | ✅ |
Request Metrics
https://requestmetrics.com
👍 Near real-time continuous monitoring
👍 Detailed reporting with zero sampling
👎 Can be expensive ($200/mo for 1 million sessions)
Grafana
Prometheus
GET /metrics
POST /analytics
Analytics Service
https://github.com/GoogleChrome/web-vitals
import webVitals from 'web-vitals';
webVitals.getTTFB(sendToAnalytics);
webVitals.getFCP(sendToAnalytics);
webVitals.getLCP(sendToAnalytics);
webVitals.getFID(sendToAnalytics);
webVitals.getINP(sendToAnalytics);
webVitals.getCLS(sendToAnalytics);
export const sendToAnalytics =
data => navigator.sendBeacon('/analytics', JSON.stringify(data));
import { Summary } from 'prom-client';
const ttfb = new Summary({ name: `ttfb`, labelNames: [`path`] });
const fcp = new Summary({ name: `fcp`, labelNames: [`path`] });
const lcp = new Summary({ name: `lcp`, labelNames: [`path`] });
const fid = new Summary({ name: `fid`, labelNames: [`path`] });
const inp = new Summary({ name: `inp`, labelNames: [`path`] });
const cls = new Summary({ name: `cls`, labelNames: [`path`] });
express()
.post(`/analytics`, (req, res) => {
const { path, body: { metric, value } } = req;
switch (metric) {
case `TTFB`: ttfb.labels(path).observe(value); break;
case `FCP`: fcp.labels(path).observe(value); break;
case `LCP`: lcp.labels(path).observe(value); break;
case `FID`: fid.labels(path).observe(value); break;
case `INP`: inp.labels(path).observe(value); break;
case `CLS`: cls.labels(path).observe(value); break;
}
res.status(200).send({});
})
.listen(8000);
Amazon S3
Snowflake
POST /analytics
Analytics Service
👍 Near real-time continuous monitoring
👍 Free open source tooling
👎 Ongoing maintenance cost
👎 Almost certainly a bad idea
✅ User-Centric Performance
✅ Capture Metrics from End User Devices
✅ Continuous Monitoring
Progressive Performance (Chrome Dev Summit 2016) - Alex Russel
The Mobile Web: MIA - Alex Russel
The Performance Inequality Gap, 2024 - Alex Russel
The Baseline for Web Performance in 2022 - Line Engineering Blog
Timing for bringing page experience to Google Search - Google Search Central Blog
Dark Silicon and the end of multicore scaling - IEEExplore
Speed Test Global Index - United States
Fast Load Times: Techniques for improving page load experience - web.dev
Site Speed Topography - CSS Wizardry
Measure What You Impact, Not What You Influence - CSS Wizardry
https://developers.google.com/web/tools/lighthouse
https://calibreapp.com/features/pull-request-reviews
https://developers.google.com/web/tools/chrome-user-experience-report
https://calibreapp.com/tools/core-web-vitals-checker
https://github.com/GoogleChrome/web-vitals
https://macklin.me/7-useful-tools-for-monitoring-web-performance-user-experience