Measured,
not claimed.

Fast is a number. This page shows the Core Web Vitals of this site from real visits, lab numbers for my projects, and the fixes the numbers led to.

Real visits

Core Web Vitals of aliabbas.net

Each page load in production reports its metrics once, when the tab is hidden. The number is the 75th percentile (p75), as Google uses: three of four visits are this fast or faster. Colors use Google’s thresholds.

37 page loads in the last 28 days.

Core Web Vital

Largest Contentful Paint (LCP)

How long until the main content shows.

2.43 sGood

p75 of 37 samples · good ≤ 2.50 s, poor > 4.00 s

Mobile p75
2.61 s
Desktop p75
13 of 20

Core Web Vital

Interaction to Next Paint (INP)

How fast the page responds to a tap, click or key.

Collecting: 8 of 20 page loads. With fewer samples, one slow visit moves the number too much to show it.

Mobile p75
0 of 20
Desktop p75
8 of 20

Core Web Vital

Cumulative Layout Shift (CLS)

How much the layout moves while the page loads.

0.00Good

p75 of 37 samples · good ≤ 0.10, poor > 0.25

Mobile p75
0.00
Desktop p75
13 of 20

Loading

First Contentful Paint (FCP)

How long until the first text or image shows.

1.41 sGood

p75 of 37 samples · good ≤ 1.80 s, poor > 3.00 s

Mobile p75
1.02 s
Desktop p75
13 of 20

Loading

Time to First Byte (TTFB)

How long until the server starts to answer.

740 msGood

p75 of 37 samples · good ≤ 800 ms, poor > 1.80 s

Mobile p75
381 ms
Desktop p75
13 of 20

What the numbers changed

The page waited for JavaScript to show itself

Lighthouse showed a long delay between the first paint and the largest paint. The cause: the entrance animations ran in JavaScript. The server sent each page at opacity 0, so the heading stayed hidden until the scripts loaded and hydrated.

The fix moves the entrances to CSS. The server now sends the page visible, and the animation starts with the first paint. Content below the fold still reveals on scroll: the browser hides it before its first paint, while it is off screen. I also self-host the mono font, so no stylesheet from another origin blocks the first paint.

Largest Contentful Paint before and after the fix
PageLCP beforeLCP afterChangeFCP before → after
/10.36 s5.52 s−47%4.62 s → 4.70 s
/chat7.97 s5.29 s−34%4.50 s → 4.57 s
/work6.86 s4.10 s−40%3.63 s → 3.62 s
/skills5.43 s3.98 s−27%3.48 s → 3.50 s

Measured 2026-10-28. Both production builds ran in the local Workers runtime, which does not compress files, so all numbers are slower than live. Headless Chromium with real throttling: 150 ms latency, 1.6 Mbps, 4× slower CPU, a 412 × 823 phone viewport, no cache. LCP and FCP come from the browser’s PerformanceObserver. Median of three loads per page.

What the numbers changed

A browser game downloaded too much, every time

I measured Averno, my 3D browser game, and two problems stood out. The 3D models were the largest part of the download, because they were stored uncompressed and Cloudflare does not compress glTF files. And every file had a fixed name, so the browser had to ask the server about each one again on every visit.

The models now use meshopt compression. Positions stay exact; normals move by 0.3° on average and 1.1° at most. When a model loads, the game unpacks it back into the format that its code expects. Screenshots of the same scene before and after differ in fewer than 0.03% of pixels. Each model and texture now has a content hash in its name, so the browser keeps it for a year and asks for nothing on a repeat visit. The cost: 16 KB more JavaScript for the decoder.

Averno download before and after the fix
WhatBeforeAfterChange
Models1,405 KB507 KB−64%
JavaScript393 KB409 KB+4%
First visit, desktop2,762 KB1,877 KB−32%
First visit, phone2,318 KB1,429 KB−38%
Repeat visit, requests to the server402—

Measured 2026-10-27, on the live game, before and after the deploy. Headless Chromium, empty cache, bytes over the network, three runs per profile. The phone is an emulated touch device, which starts on medium quality. For the repeat visit, the old version ran in the local Workers runtime, which sends the same cache headers as Cloudflare. Frame rate is not measured: the test machine draws WebGL in software.

Lab

Lighthouse on the live sites

The same test for this site and two of my projects: Lighthouse 12.8.2, mobile preset, median of 3 runs, on 2026-10-28. For this site, “before” rows ran before the fix shipped and “after” rows ran after it, on the same day.

Lighthouse lab results for the live sites
SiteScoreLCPFCPCLSBlocking time
aliabbas.net — home, before

The test machine has no GPU, so the WebGL globe draws in software and blocks the main thread. That blocking time describes the test machine, not a phone, and it also slows the simulated LCP.

306.45 s4.06 s0.00Not shown
aliabbas.net — home, after

The same note about the globe applies.

315.88 s3.73 s0.00Not shown
aliabbas.net — chat, before724.42 s4.35 s0.000 ms
aliabbas.net — chat, after853.49 s3.13 s0.0044 ms
aliabbas.net — work, after873.24 s2.94 s0.0212 ms
aliabbas.net — skills, after863.25 s3.10 s0.0016 ms
Pico942.90 s1.75 s0.000 ms
Whimcall873.23 s2.63 s0.000 ms

Method

How I measure

  • Real visits use Google’s web-vitals library. It loads when the browser is idle, after the page, and sends one small request when the tab is hidden. It never slows the page that it measures.
  • The server stores the metric, its value, the route (for example /notes/$slug, never the full URL), and mobile or desktop. It stores no IP address and no browser details, and deletes rows after 90 days. See the privacy page.
  • The server rates each value itself, with Google’s thresholds, and rejects values that cannot be real. The page shows a p75 only from 20 samples, because with fewer samples one slow visit moves the number too much.
  • Lab numbers are the median of three runs. A single run can be off by hundreds of milliseconds.