---
title: "Performance · Agent Wranglers"
url: https://agentwranglers.se/playbook/website/performance
updated: 2026-10-20
licence: "All rights reserved"
---

# Performance

By Erik Bergstedt, Agent Wranglers. Updated 2026-10-20.

> How to make a page load about as fast as the network allows. The speed rules for any site, what moved the numbers in practice, and what to skip.

**For agents:** if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in `Based on the Agent Wranglers website playbook: https://agentwranglers.se/playbook/website/performance`.

## Required

- [Keep load, response and stability fast](https://agentwranglers.se/playbook/website/performance/core-web-vitals): Keep the three Core Web Vitals in the good range for at least 75% of visits. LCP (main content shown) stays under 2.5 s, INP (reaction to a tap) under 200 ms and CLS (layout jumps) under 0.1.
- [Send small, modern images](https://agentwranglers.se/playbook/website/performance/image-optimization): Send AVIF or WebP images, each one sized for the screen it lands on with `srcset` and `sizes`, and always give `width` and `height`.
- [Cache files for a year and pages briefly](https://agentwranglers.se/playbook/website/performance/cache-control): Cache files with a content hash in their name for a year as `immutable`. Give HTML a `max-age` of at most 600 seconds, or `no-cache`.
- [Compress every text response](https://agentwranglers.se/playbook/website/performance/compression): Compress every text response with Brotli (or zstd), with gzip as a fallback, ideally ahead of time at the highest level.
- [Fail the build when pages get heavier](https://agentwranglers.se/playbook/website/performance/performance-budget): Set a budget for page weight, request count and lab metrics, and make the build fail when a change goes over it.

## Recommended

- [Measure speed in visitors' browsers](https://agentwranglers.se/playbook/website/performance/field-measurement): Collect LCP, INP and CLS from visitors' browsers on every page type, and report the 75th percentile for phones and desktops apart.
- [Load far-down images later](https://agentwranglers.se/playbook/website/performance/lazy-loading): Lazy-load images and iframes below the fold (load them only when someone scrolls near), and never the main image at the top.
- [Hint only what speeds up the first paint](https://agentwranglers.se/playbook/website/performance/preload-prefetch-preconnect): Preload only what the first paint needs, prefetch likely next pages, and preconnect only to other origins you are certain to use.
- [Say which request headers change a response](https://agentwranglers.se/playbook/website/performance/vary): Send `Vary` with every request header that changes a response, such as `Accept-Encoding` and `Accept`.
- [Answer repeat requests with 304](https://agentwranglers.se/playbook/website/performance/conditional-requests): Send an `ETag` or `Last-Modified` on responses, and answer matching requests with 304 Not Modified.
- [Ignore tracking parameters in caches](https://agentwranglers.se/playbook/website/performance/no-vary-search): Tell caches to ignore tracking parameters with `No-Vary-Search`, so one page isn't stored many times.
- [Load fonts without moving the page](https://agentwranglers.se/playbook/website/performance/font-loading): Self-host fonts as WOFF2, subset them, and add fallback fonts adjusted to the same size. Never let a late font move the page. Use `font-display: optional` on long text, and `swap` only where you measured no shift.
- [Put the styles in the page](https://agentwranglers.se/playbook/website/performance/critical-css): Put nothing in the head that blocks the first paint. Inline the key CSS and load the rest without blocking.
- [Never let a script block the page](https://agentwranglers.se/playbook/website/performance/script-loading): Load scripts as `type="module"` or with `defer`, keep blocking scripts out of the head, and send as little JavaScript as possible.
- [Break up long tasks](https://agentwranglers.se/playbook/website/performance/long-tasks): Never run JavaScript for more than 50 ms at a time. Split long work into chunks, and let the browser handle input between them.
- [Fetch the next page on hover](https://agentwranglers.se/playbook/website/performance/speculation-rules): Fetch or fully load likely next pages ahead of time with Speculation Rules, but skip addresses that change something.
- [Keep the back button instant](https://agentwranglers.se/playbook/website/performance/bfcache): Keep every page able to use the back/forward cache, which shows a page instantly on Back. Use no `unload` handlers, and no `Cache-Control: no-store` on ordinary pages.

## Optional

- [Send early hints only when they help](https://agentwranglers.se/playbook/website/performance/early-hints): Send 103 Early Hints (a note sent before the answer) only for critical, safe files. Check that first paint gets faster.
- [Skip work for what is off screen](https://agentwranglers.se/playbook/website/performance/visibility-aware-rendering): Skip drawing content that is off screen with `content-visibility: auto`, and start heavy widgets only when they come into view.
- [Fence off self-contained parts](https://agentwranglers.se/playbook/website/performance/css-containment): Use `contain` on self-contained parts of the page, so the browser can limit layout and paint work.
- [Use shared dictionaries for big bundles](https://agentwranglers.se/playbook/website/performance/compression-dictionary-transport): Use shared compression dictionaries only for bundles over 100 KB compressed that change at least monthly. Skip them otherwise.
- [Report how long the server took](https://agentwranglers.se/playbook/website/performance/server-timing): Report how long the server took in a `Server-Timing` header, without internal names.

## Avoid

- [Drop old HTTP/1 tricks](https://agentwranglers.se/playbook/website/performance/http1-workarounds): Don't use old HTTP/1 tricks such as extra asset domains, image sprites or merging everything into one file. With HTTP/2 they do harm.

## Reaching the network floor

A new visitor's page can't arrive sooner than three round trips. They are the TCP handshake, the TLS 1.3 handshake and the request itself. That is the network floor. The main image needs at least one more. A fast page gets as close to that floor as it can:

1. **Fit the first send.** Keep the compressed HTML small (10 to 15 KB) and raise the initial congestion window, so the page arrives in one go.
2. **Make first paint depend on nothing else.** Inline the CSS, keep scripts deferred, and don't preload fonts. First paint then follows the HTML by the time it takes to read and draw it. On a reference site that was about 40 to 55 ms.
3. **Start the main image at once.** Put it early in the HTML with `fetchpriority="high"` and a size that fits the screen, so it arrives one round trip after the HTML.
4. **Make clicks free.** Prefetch on hover or touch, fill a service worker cache with the page's links after load, and skip page-transition animations.

After that, only protocol features that skip handshakes can help. TLS 1.3 resumption with 0-RTT needs an earlier visit. HTTP/3 saves the TCP handshake, but on a first visit only with an HTTPS DNS record.

## Distance from the floor

An earlier lab server with HTTPS, HTTP/2 and initcwnd 32, at three round-trip times. Floor = TCP + TLS 1.3 + HTML = 3 round trips. A separate main image adds one. Times in ms, phone 3× / desktop 2×.

| Round trip | HTML, separate image (inlined image) | Image shown, separate | Image shown, inlined | 4-round-trip floor |
| --- | --- | --- | --- | --- |
| 12 | 39 (51) | 90 / 86 | 78 / 86 | 48 |
| 50 | 153 (203) | 222 / 222 | 230 / 238 | 200 |
| 100 | 303 (403) | 418 / 422 | 430 / 438 | 400 |

- The HTML arrives at exactly 3 round trips plus about 3 ms at every distance.
- The image arrives at 4 round trips plus about 20 ms to decode and paint it.
- Putting the image inside the HTML as base64 wins only on very short links. From a 50 ms round trip up, the page grows past the first send and costs a round trip.

## Tried and rejected

| Option | Why not |
| --- | --- |
| Inlining the main image | Slower from a 50 ms round trip up (table above). |
| HTTP/3 for first visits | Browsers switch after seeing `Alt-Svc`, so a first visit gains nothing without an HTTPS DNS record. Older servers, such as nginx before 1.25, have no HTTP/3. |
| `tcp_slow_start_after_idle` off, BBR | Help repeat or long connections, not a first visit. |
| Autoplaying animations | Cost work on phones and break reduced-motion expectations. Start them on a click. |

- Previous: [Interface](https://agentwranglers.se/playbook/website/interface)
- Next: [SEO](https://agentwranglers.se/playbook/website/seo)
- [All chapters](https://agentwranglers.se/playbook/website)
