Last updated: Apr 29, 2026

Loading Optimization

Page load speed depends on how efficiently assets travel from server to browser. The optimizations below target different parts of that journey — reducing bytes on the wire, avoiding unnecessary requests, and loading resources in the right order.

Compression

Servers can compress responses before sending them. The browser advertises supported encodings via the Accept-Encoding header, and the server picks one.

EncodingNotes
gzipUniversally supported, good baseline compression
Brotli (br)15–25 % smaller than gzip for text assets; supported by all modern browsers over HTTPS

Enable Brotli with a gzip fallback. Most CDNs and reverse proxies (Nginx, Cloudflare) handle this automatically.

Caching

Proper cache headers eliminate repeat downloads entirely.

HeaderRole
Cache-Control: max-age=31536000, immutableLong-lived cache for fingerprinted assets (hashed filenames)
Cache-Control: no-cacheAlways revalidate with the server before using the cached copy
ETag / Last-ModifiedLet the server respond with 304 Not Modified when the resource has not changed
ExpiresLegacy equivalent of max-age; Cache-Control takes precedence when both are present

Fingerprint static assets (e.g. app.3f2a1b.js) and serve them with a long max-age. HTML documents themselves should use no-cache so users always get the latest entry point.

HTTP/2

HTTP/2 multiplexes many requests over a single TCP connection, removing the head-of-line blocking that limited HTTP/1.1 to ~6 parallel requests per origin. This makes several HTTP/1.1-era hacks unnecessary:

  • File concatenation — bundling all CSS or JS into a single file is no longer required for performance; smaller, granular files work well with multiplexing.
  • Image sprites — individual images can be served without the overhead of extra connections.
  • Domain sharding — splitting assets across subdomains to increase parallelism is counterproductive under HTTP/2.

If the server still runs HTTP/1.1, concatenating CSS and JS into single files and using image sprites remain valid optimizations.

Resource Hints

<link
  rel="preload"
  href="/fonts/inter.woff2"
  as="font"
  type="font/woff2"
  crossorigin
/>
<link rel="prefetch" href="/next-page.js" />
<link rel="preconnect" href="https://api.example.com" />
<link rel="dns-prefetch" href="https://cdn.example.com" />
<link rel="modulepreload" href="/src/app.js" />
<link rel="prerender" href="https://my-app.com/pricing" />
HintWhen to use
preloadResource is needed for the current page but discovered late (fonts, critical scripts behind an @import)
prefetchResource is likely needed for the next navigation; fetched at idle priority
preconnectYou know a request to an origin is coming soon; performs DNS lookup, TCP handshake, and TLS negotiation early
dns-prefetchLighter alternative to preconnect — resolves DNS only; useful for third-party origins that may be needed
modulepreloadLike preload but for ES modules — also parses and compiles the module so it is ready to execute immediately
prerenderLoads and renders an entire page in a hidden tab; use only when very confident the user will navigate there next

preconnect is most valuable for origins used above the fold (API host, font CDN). Limit it to 4–6 origins — each open connection has a cost. Fall back to dns-prefetch for lower-priority or less-certain origins, since it carries almost no overhead. prerender is the heaviest hint — it downloads the full page and its subresources. Use it for at most one URL and only when navigation is highly likely (e.g. a dominant next step in a funnel).

Code Delivery

Minification and tree-shaking are covered in Build Pipeline. Two additional techniques reduce the JavaScript a page actually downloads:

Code splitting breaks a bundle into smaller chunks loaded on demand. Route-based splitting is the most common approach — each page only downloads its own code.

const Settings = lazy(() => import("./pages/Settings"));

Critical CSS inlines the styles needed for above-the-fold content directly in the <head>, then loads the full stylesheet asynchronously. This avoids a render-blocking request for the entire CSS file.

<head>
  <style>
    /* inlined critical styles */
  </style>
  <link
    rel="preload"
    href="/styles.css"
    as="style"
    onload="this.rel='stylesheet'"
  />
</head>

Images

Images are typically the heaviest assets on a page. That said, raw byte size can be misleading — 300 KB of JavaScript costs far more than 300 KB of an image because JS must be parsed, compiled, and executed, while an image only needs to be decoded and painted.

TechniqueDetails
Modern formatsWebP and AVIF offer significantly better compression than JPEG/PNG at comparable quality
CompressionRun images through tools like sharp, squoosh, or imagemin before deployment
Responsive imagesUse srcset and sizes to serve appropriately sized images per viewport
SpritesCombine small icons into a single image to reduce requests (HTTP/1.1 only — unnecessary with HTTP/2)

Lazy loading defers off-screen images so they do not block the initial page load.

<img src="photo.webp" loading="lazy" alt="description" />

The native loading="lazy" attribute works in all modern browsers. For more control (e.g. fade-in effects, custom thresholds), the Intersection Observer API can trigger loading manually:

const observer = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    if (entry.isIntersecting) {
      const img = entry.target as HTMLImageElement;
      img.src = img.dataset.src!;
      observer.unobserve(img);
    }
  }
});

document
  .querySelectorAll("img[data-src]")
  .forEach((img) => observer.observe(img));

Fonts

PracticeReason
Serve WOFF2 with WOFF fallbackWOFF2 compresses ~30 % better than WOFF; WOFF covers legacy browsers
font-display: swapShows a system font immediately, swaps to the custom font once loaded — avoids invisible text
Preload critical font filesFonts are discovered late (after CSS is parsed); preloading starts the download earlier
@font-face {
  font-family: "Inter";
  src:
    url("/fonts/inter.woff2") format("woff2"),
    url("/fonts/inter.woff") format("woff");
  font-display: swap;
}

Script Loading

Where and how a <script> tag appears determines when it runs relative to HTML parsing.

AttributeBehavior
(none)Blocks parsing while the script downloads and executes
asyncDownloads in parallel with parsing; executes immediately when ready (order not guaranteed)
deferDownloads in parallel with parsing; executes after the document is parsed, before DOMContentLoaded (order preserved)
End of <body>Equivalent to defer in practice — parsing finishes before the script is encountered

Use defer for scripts that depend on the DOM or on each other. Use async for independent scripts like analytics.

Perceived Performance

Skeleton screens display placeholder shapes that mirror the layout of incoming content. Unlike a blank page or a spinner, they give users an immediate sense of structure. The skeleton is replaced with real content as data arrives — this reduces perceived wait time even when actual load time is the same.