what javascript costs users, measured in milliseconds they will never get back
The browser is a performance machine. Given only HTML, it parses, layouts, and paints in under 100ms on modest hardware.
The question is not whether your page can be fast. It can. The question is whether you will allow it to be.
the metrics
TTFBTime to First Byte
How long until the server responds with the first byte of HTML.
good< 200mspoor> 600ms
killerserver-side rendering frameworks that hydrate before responding
A static HTML file served from a CDN achieves < 20ms globally. A Next.js app with client hydration routinely achieves 400ms on a good day.
FCPFirst Contentful Paint
When the browser first renders something visible.
good< 1.8spoor> 3.0s
killerrender-blocking scripts in <head>
Every <script> tag without async or defer halts the parser. The browser waits. The user stares at white. This is load-order discipline. Most developers do not have it.
TBTTotal Blocking Time
Sum of time the main thread was blocked, preventing user interaction.
good< 200mspoor> 600ms
killerJavaScript bundles over 50kb
Any task that runs longer than 50ms on the main thread is a long task. Long tasks block input. A 300kb React bundle parses and executes on the main thread. The user clicks. Nothing happens. They click again. Still nothing. This is your framework working as intended.
TTITime to Interactive
When the page becomes reliably interactive.
good< 3.8spoor> 7.3s
killerhydration
Hydration is the process of taking server-rendered HTML and attaching JavaScript event listeners to it. The HTML already exists. The page already looks right. The user tries to click. Nothing happens — because the framework is still downloading its runtime and re-rendering what the server already rendered.
CLSCumulative Layout Shift
Total visual instability as elements move after initial render.
good< 0.1poor> 0.25
killerimages without dimensions, dynamically injected content, web fonts
The user is about to click a link. A font loads. Text reflows. The link moves 40px down. They click the ad instead. This is not an accident. This is what happens when you load content after the layout.
LCPLargest Contentful Paint
When the largest visible element finishes rendering.
good< 2.5spoor> 4.0s
killerhero images not preloaded, lazy-loaded above-the-fold content
The largest element is usually the hero image or the headline. If you lazy-load the hero image — the one that is immediately visible — you have optimized for a case that does not exist.
small sins with measurable consequences
loading analytics before content
15–45ms blocked
the data you are collecting is about users who cannot see your page yet.
polyfilling modern APIs for browsers that support them natively
8–80kb
check the support table first. then check the date. then check whether your users are running IE.
using JavaScript to set CSS variables
layout recalculation
CSS custom properties respond to media queries. they do not need a resize listener.
importing entire icon libraries for three icons
40–200kb
SVG is 2001 technology. you can inline three icons. they are not heavy.
client-side routing on a site with five pages
25–60kb runtime
the browser already knows how to navigate. it has known since 1993.
TypeScript enums compiled to IIFEs
extra bytes per enum
const objects with 'as const' compile to nothing. enums compile to self-executing functions.
useEffect to set document title
one flicker
the <title> tag exists. it is server-rendered. it does not require a hook.
on hydration: the framework community has responded to the hydration problem by inventing
partial hydration, islands architecture, streaming SSR, resumability, and progressive hydration.
these are all correct responses to a problem that did not exist before the frameworks that created them.
the browser has shipped a fast, zero-kilobyte solution since 1996: HTML.
this page: system fonts. no javascript. no framework runtime. no hydration.
TTFB governed by the CDN, not by a framework. TBT is zero.
you are reading this in the time it takes another page to download its router.