Skip to content

Improving web application performance

Technique · Chapter 10

Techniques to speed up web applications (not exhaustive):

  • HTTP caching
  • Compression
  • Minification
  • Bundling
  • HTML/critical-rendering-path optimization
  • HTTP/2
  • Content delivery networks (CDNs)
  • Web font optimization

Loading a page needs many client↔server roundtrips; caching reusable resources (CSS/JS shared across pages, or needed on return visits) avoids re-transferring them. Each server response must carry the right HTTP header directives; the cache policy depends on the app and the data type.

  • Validation token (ETag): when a cached response has expired but hasn’t actually changed, the client sends the token; if unchanged, the server returns 304 Not Modified and the browser renews its cached copy instead of re-downloading.
  • Cache-Control directives:
    • no-store — never cache (browsers and intermediaries, e.g. CDNs); for sensitive data.
    • private — cache in the user’s browser but not in intermediate caches.
    • no-cache — may cache, but must revalidate with the server (via validation token) before reuse.
    • max-age — max seconds a response may be reused, relative to the request time.
  • Cache busting: to invalidate before expiry, change the resource URL (embed a version number or file fingerprint in the filename) to force a fresh download.

Use an algorithm to remove redundancy → smaller files, faster transfer, better bandwidth use. Servers and browsers implement it already; developers just configure the server (both sides must agree on the algorithm). Two main kinds:

For images/video/audio (often the bulk of page bytes):

  • Lossless — all original bytes recover on decompression. Required for text, source code, executables. Image formats: GIF (use for animation) and PNG (preserve fine detail).
  • Lossy — some bytes are lost; smaller files, tunable quality/size trade-off; often no perceptible difference. Good for images/video/audio. Image format: JPEG.
  • Server compresses the HTTP message body; it stays compressed through intermediaries until the client decompresses it.
  • Requires content negotiation — browser and server agree on the algorithm.
  • gzip is the most common (lossless, great on text); Brotli (br) is a newer open-source lossless library gaining support.
  • Don’t double-compress already-compressed files (image/video/audio) — no gain, and the result can be slightly larger.
  • Remove unnecessary/redundant characters (whitespace, comments, long names) from JS/HTML/CSS without changing functionality → smaller files, faster loads.
  • Result is less human-readable, so keep two versions: unminified for debugging, minified for deployment (e.g. invoice.js vs invoice.min.js).
  • Minify before compressing.
  • Combine multiple same-type files (JS or CSS) into one file (a.k.a. concatenation) → fewer HTTP requests → faster loads. Complementary to minification.
  • Mainly a HTTP/1.1 technique. Browsers open multiple connections per host (commonly ~6); bundling reduces requests/connections needed.
  • Downside: coarser cache granularity — changing any file in a bundle re-downloads the whole bundle (and a shared file can invalidate multiple bundles).
  • Domain sharding: split resources across subdomains (shard1.example.com, shard2.example.com) so each shard gets the max connections → more parallel downloads. Overhead: extra DNS lookups, extra resources both ends, and manual resource splitting.

Latest version of the protocol — not a rewrite; same status codes, verbs, methods, most headers. Key difference: HTTP/2 is binary (vs HTTP/1.1 textual) → more compact, faster to parse, less error-prone. This enables:

  • Multiplexing — multiple requests/responses asynchronously over a single TCP connection. HTTP/1.1 lacked this, which is why the workarounds existed. With HTTP/2:
    • Stop bundling into a few big bundles — prefer granular assets (no bundling, or many small related bundles) for per-file cache policies and less costly invalidation.
    • Domain sharding is no longer needed — multiplexing already parallelizes over one connection; shard overhead isn’t worth it.
  • Server push — server proactively sends resources it knows the client will need (similar to inlining, but assets stay in separate files with their own cache policies). Caveats: don’t push too much (can delay render and hurt perceived performance) or push assets the client already has; may need experimentation, cache digests, and server-side mitigation.
  • Header compression — HTTP/2 compresses headers with HPACK (Huffman coding, lossless), reducing size/latency. HPACK resists compression attacks like CRIME (which plain-text HTTP/1.x headers were exposed to).
  • Implementing: client just needs a supporting browser (most modern ones do). Servers vary in setup and typically must support both HTTP/1.1 and HTTP/2, falling back to 1.1 when the client can’t do 2.
  • A geographically distributed group of servers delivering content quickly by serving from a node closer to the user → lower network latency, faster loads.
  • Great for static content: JS, HTML, CSS, images, video, audio. Also improve load times via load balancing, caching, minification, and file compression.
  • Boost reliability/redundancy (traffic load-balanced across servers; reroute around failing servers/data centers) and security (mitigate DDoS, maintain up-to-date TLS/SSL certificates).
  • Good typography aids UI, readability, accessibility, branding. Beyond legacy web-safe fonts, CSS font-family lists fallbacks (browser uses the first available) and web fonts let you download font files so any supporting browser can render your chosen fonts (text stays selectable, searchable, zoomable, crisp at any resolution).
  • But web fonts add resources — treat them as part of your performance strategy. Minimize the number of fonts/variants used.
  • No standard font format → currently support four formats per font: WOFF 2.0, WOFF, TTF, EOT. Use @font-face to declare the font + URL.
  • Compress fonts: WOFF 2.0/WOFF have built-in compression; TTF/EOT don’t, so the server should compress them.
  • Use the unicode-range property in @font-face to subset large Unicode fonts so only needed characters download.
  • Fonts change rarely → cache them with a long-lived policy plus a validation token so they renew rather than re-download when unchanged.

Optimizing the critical rendering path (CRP)

Section titled “Optimizing the critical rendering path (CRP)”

The CRP is the set of steps between the browser receiving bytes (HTML/CSS/JS) and painting pixels on screen:

  1. Build the DOM (from HTML) and the CSSOM (from CSS).
  2. Combine them into a render tree (content + style of what’s visible).
  3. Layout — compute size/position of visible elements.
  4. Paint — draw pixels using the layout.

Optimizing the CRP = minimizing time through these steps, focused on content above the fold (visible without scrolling):

  • Prevent render blocking: identify the resources truly needed for initial render and get them to the client fast. HTML and CSS are render-blocking (needed for DOM/CSSOM), so prioritize them; compression and caching help.
  • Defer the rest: non-critical / below-the-fold resources can be eliminated, deferred, or loaded asynchronously (e.g. defer non-initial images; load blocking JS asynchronously to avoid stalling DOM construction).
  • Result: the page becomes visible and usable almost immediately → lower bounce rate, higher conversion rate.
  • Software Architect’s Handbook (Packt, 2018), Ch.10 “Improving web application performance”, pp. 768-793.