Web Performance Basics: From First Byte to Interactive

May 28, 20268 min read

Why Performance Matters

Every 100 ms of delay costs you conversions. Google reports that 53% of mobile users abandon sites that take longer than 3 seconds to load. Performance is not a luxury — it is the foundation of user experience and search ranking.

This guide walks through the metrics, the rendering pipeline, and the strategies you need to ship fast pages.

Core Performance Metrics

Modern performance measurement focuses on what users actually perceive. These are the metrics that matter:

MetricFull NameMeasuresGoodPoor
FPFirst PaintFirst pixel on screen≤ 1 s> 3 s
FCPFirst Contentful PaintFirst text or image renders≤ 1.8 s> 3 s
LCPLargest Contentful PaintMain content visible≤ 2.5 s> 4 s
TTITime to InteractivePage responds to input≤ 3.8 s> 7.3 s
CLSCumulative Layout ShiftVisual stability≤ 0.1> 0.25
INPInteraction to Next PaintInput responsiveness≤ 200 ms> 500 ms
TTFBTime to First ByteServer response speed≤ 800 ms> 1.8 s

Google's Core Web Vitals (LCP, CLS, INP) directly influence search rankings. If you track nothing else, track these three.

How the Metrics Relate

Metrics fire in sequence:

  1. TTFB — Server responds
  2. FP — Browser paints the first pixel (often a blank screen)
  3. FCP — First useful content appears (text, image, canvas)
  4. LCP — Largest above-the-fold element finishes rendering
  5. TTI — JavaScript event handlers attached, page is usable

Each metric depends on the previous one. A slow TTFB cascades into slow everything else.

The Critical Rendering Path

The critical rendering path is the sequence of steps the browser takes to turn HTML, CSS, and JavaScript into pixels on screen.

Step-by-Step

  1. Parse HTML → Build the DOM tree
  2. Fetch and parse CSS → Build the CSSOM tree
  3. Combine DOM + CSSOM → Render tree (visible nodes only)
  4. Layout → Calculate geometry (size, position)
  5. Paint → Fill pixels (colors, images, borders)
  6. Composite → Layer compositing for final screen output

JavaScript can interrupt this path. When the parser hits a <script> tag, it stops building the DOM until the script executes (unless the script is async or defer).

Optimizing the Path

TechniqueWhat It DoesImpact
Inline critical CSSRenders above-the-fold content without waiting for external CSSFaster FCP
defer on <script>Parses HTML first, executes scripts after DOM readyFaster FCP/TTI
async on <script>Downloads in parallel, executes whenever readyGood for analytics
Preload key resources<link rel="preload"> fetches earlyFaster LCP
Avoid render-blocking CSSSplit CSS into critical and deferred chunksFaster FCP
<!-- Critical CSS inlined in <head> -->
<style>body{margin:0;font-family:sans-serif}.hero{font-size:2rem}</style>

<!-- Non-critical CSS loaded asynchronously -->
<link rel="preload" href="styles.css" as="style"
      onload="this.rel='stylesheet'">

<!-- Scripts deferred to avoid blocking parsing -->
<script src="app.js" defer></script>

Resource Prioritization

Not all resources are equally important. The browser assigns priorities based on type and position, but you can override them.

Priority Hints

Use fetchpriority to tell the browser what matters most:

<!-- High priority: hero image above the fold -->
<img src="hero.jpg" fetchpriority="high" alt="Hero image">

<!-- Low priority: below-the-fold content -->
<img src="supplementary.jpg" fetchpriority="low" alt="Supplementary image">

<!-- High priority: critical script -->
<script src="core.js" fetchpriority="high"></script>

Resource Hints

HintPurposeWhen to Use
dns-prefetchResolve DNS earlyThird-party domains
preconnectEstablish connection (DNS + TCP + TLS)Critical third-party origins
preloadFetch resource early with high priorityHero images, critical fonts, key CSS
prefetchFetch for future navigationNext-page resources
modulepreloadPreload ES modulesCritical JS modules
<!-- Preconnect to your API and CDN -->
<link rel="preconnect" href="https://api.example.com">
<link rel="preconnect" href="https://cdn.example.com">

<!-- Preload the LCP image -->
<link rel="preload" as="image" href="hero.webp">

Caching Strategies

Caching prevents repeat downloads. Combine multiple layers for maximum effect:

Browser Cache (HTTP Headers)

HeaderPurposeTypical Value
Cache-ControlDirectives for caching behaviormax-age=31536000, immutable
ETagValidation token for revalidationAuto-generated by server
Last-ModifiedTimestamp for conditional requestsFile modification date

For static assets with hashed filenames, use aggressive caching:

Cache-Control: public, max-age=31536000, immutable

The immutable flag tells the browser to skip revalidation entirely — the filename changes when the content changes, so the cached version is always valid.

Service Worker Cache

Service workers give you programmatic control over caching:

// Cache-first strategy for static assets
self.addEventListener('fetch', (event) => {
  if (event.request.url.includes('/assets/')) {
    event.respondWith(
      caches.match(event.request).then((cached) => {
        return cached || fetch(event.request)
      })
    )
  }
})

CDN Cache

CDNs serve your content from edge servers close to users. Benefits include:

  • Reduced TTFB (response comes from nearby edge, not origin)
  • Automatic compression (gzip/Brotli)
  • DDoS absorption
  • SSL termination offloaded from origin

Configure CDN cache based on content type: long TTL for static assets, short TTL or stale-while-revalidate for HTML.

Performance Budgets

A performance budget sets numeric limits on metrics and resource sizes. It prevents gradual bloat from eroding speed over time.

Setting a Budget

ResourceSuggested BudgetRationale
Total page weight< 1 MB (compressed)Fits in slow 3G within 5 s
JavaScript< 200 KB (compressed)Keeps TTI under 5 s on mobile
CSS< 50 KB (compressed)Fast CSSOM construction
Images< 500 KB totalLargest content type, easiest to optimize
LCP< 2.5 sCore Web Vitals "good" threshold
CLS< 0.1No jarring layout shifts
TTI< 3.8 sUsers can interact quickly

Enforcing Budgets

Integrate budget checks into your CI pipeline:

// performance-budget.js (Lighthouse CI config)
module.exports = {
  ci: {
    assert: {
      assertions: {
        'categories:performance': ['error', { minScore: 0.9 }],
        'first-contentful-paint': ['error', { maxNumericValue: 1800 }],
        'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
        'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
        'total-byte-weight': ['warn', { maxNumericValue: 1000000 }],
      },
    },
  },
}

Builds fail when budgets are exceeded, forcing the team to address performance regressions immediately.

Quick Wins Checklist

  • Compress all text resources with Brotli or gzip
  • Serve images in WebP or AVIF with responsive srcset
  • Defer non-critical JavaScript
  • Inline critical CSS, lazy-load the rest
  • Set Cache-Control: immutable on hashed assets
  • Preload the LCP image and critical fonts
  • Add width and height to images to prevent CLS
  • Configure a performance budget in CI

Key Takeaways

  • Track LCP, CLS, and INP — Google's Core Web Vitals that affect rankings
  • The critical rendering path determines how fast pixels appear; minimize render-blocking resources
  • Use fetchpriority, preload, and preconnect to prioritize what matters
  • Layer browser cache, service workers, and CDN for resilient caching
  • Performance budgets prevent gradual bloat — enforce them in CI
  • Every 100 ms counts: faster pages retain more users and rank higher

Try It Yourself

Minify your HTML and see the size difference with our free HTML Minifier tool. Paste your markup, configure optimization options, and get leaner output in seconds.