Findings · 2026-08-21 · By VSNARY | Emmanuel Orta · 0 views
An analytics script added 650 ms to every request: how we found it
A first-party beacon reported that 70% of our LCP was time-to-first-byte. The cause was three telemetry writes awaited before routing — next to a comment explaining why they could not be moved. The comment was wrong.
We shipped a first-party performance beacon so we could see real field data for sites the Chrome UX Report will never cover. The first real reading it produced was about our own homepage, and it was not flattering.
| LCP | 1,440 ms |
| TTFB | 1,014 ms (needs-improvement) |
| LCP sub-phases [TTFB, load delay, load duration, render delay] | [1014, 0, 0, 427] |
Seventy percent of Largest Contentful Paint was time-to-first-byte. Nothing was slow about the page. The server had simply not started answering yet.
A second of TTFB on a static-ish page is not a rendering problem, an image problem or a font problem. It is the server thinking before it speaks.
The natural experiment that located it in one request #
Before profiling anything we noticed the codebase already contained a controlled experiment, because two routes sat on opposite sides of one block of code.
- The beacon script, routed before the telemetry block.
- A trivial handler that returns a small JSON object, routed after it.
| Route | Work done | TTFB |
| Beacon script | serve a static string | 56 ms |
| Small JSON endpoint | serve a smaller static object | 700 ms |
The route doing less work took twelve times longer. The only difference between them was which side of the telemetry block they were routed on. That is a 650 ms fixed tax, and every route except one was paying it.
What the code was doing #
Three telemetry writes ran on every request. All three were network-backed. All three were awaited before any routing decision was made.
So the order of operations for every single request, including a crawler asking for robots.txt, was: write three records, wait for all three, then work out what was being asked for.
The fix is one of the oldest in the platform: a post-response hook that hands the runtime a promise to finish after the response has been sent. The response goes out immediately and the writes complete on their own time.
The comment that kept it alive #
This is the part worth writing down, because the bug was not hard. It had a note next to it explaining why it could not be fixed:
ctx is not available in this signature
The context object was the function’s third argument.
It had been there the whole time. Somebody wrote that comment while looking at a different version of the function, or wrote it from memory, and after that nobody re-checked — because a comment explaining why something is impossible is read as a decision that has already been made, not as a claim to verify.
A wrong comment is worse than no comment. No comment invites you to look. A confident wrong comment tells you not to bother, and it kept a 650 ms tax on every request alive for as long as anybody believed it.
After #
| Route | Before | After |
| Small JSON endpoint | 720 ms | 60 ms |
/ (homepage) | ~1,014 ms | 85 ms |
/policy | — | 63 ms |
Roughly eight to twelve times faster on TTFB, sitewide, from moving three calls into a function that already existed. Re-measured a day later across four routes and four samples each, the numbers hold between 49 ms and 76 ms.
Verifying that we had not just deleted the telemetry #
Making something fast by quietly breaking it is the obvious failure here, and “the requests got faster” is not evidence that the writes still happen. A post-response hook defers work; it can also silently drop it if the promise is never handed over correctly.
So we sent three test requests, spaced 2.5 seconds apart, and read the counter before and after. It went up by exactly three. The recording still works; it just no longer happens while the visitor waits.
What transfers #
- Analytics belongs after the response, not before it. Nothing in a visit log or a counter needs to complete before a byte is sent. If your framework offers a post-response hook, every write that does not affect the response body belongs in it.
- Find two routes on opposite sides of the suspect code. You rarely need a profiler to locate a fixed tax. Two endpoints where the simpler one is slower is a complete diagnosis on its own.
- Field data will find things a lab score cannot. A synthetic run from a well-connected machine reports TTFB too, but it is one sample from one place. A beacon on real visits is what made a one-second TTFB impossible to keep ignoring.
- Re-read the code next to the comment. The comment is not evidence. This one was a single argument away from being obviously false.
Method #
TTFB figures are time_starttransfer from an ordinary HTTP client with a cache-busting query parameter, taken from outside the network serving the site. Before-figures were taken while the awaited version was live; after-figures immediately following the deploy and again a day later. The field reading came from our own beacon on a real browser session, reporting the standard web-vitals attribution breakdown.
Every figure above came out of this scanner.
Point it at your own domain and see the same measurements, free.
The main product
Found this on your own site? We fix it for $749.
Scan free to see where you stand. The fix is one site, every finding implemented and re-measured, with a sealed before and after.
Questions this post answers
How was the 650 ms found?
By comparing two routes on opposite sides of the telemetry code. The one doing less work answered twelve times slower, which located a fixed 650 ms tax without a profiler.
Does crawlcheck.io use Google Analytics now?
No. The site records arrivals with its own first-party beacon, which stores a referrer hostname and nothing else, and it publishes those numbers rather than a third party's.
Related findings
Comments
Comments are read before they appear. Nothing is published automatically, and no account is needed.
Writing about this? Facts, live figures and marks — every number on that page is dated and traceable to a scan.