HomeMethod

Knowledge

Web VitalsTechniquesSnippetsAnswersImpactsBottlenecks

Browser Cache

Store static assets locally so repeat visits skip the network entirely.

Overview

Browser caching instructs visitors’ browsers to store static assets (images, CSS, JavaScript, fonts) locally for a specified period. On a return visit or when navigating to another page on the same site, those assets load from disk in microseconds instead of making a round trip to the server.

Impact grade, A+ to D: how much of the Lighthouse score this technique can move. Unaffected: TBT, CLS, TTFB.

The Problem

Without caching headers, every asset is re-requested from the server on every page load, even if it hasn’t changed. For returning visitors and for pages that share common assets (logo, stylesheet, main script), this means re-downloading identical files repeatedly — wasting bandwidth, adding latency, and increasing server load.

Performance Impact

LCP

High

cached images and scripts reduce resource chain depth

FCP

High

cached CSS and fonts load instantly on return visits

SI

repeat visits paint faster as CSS, fonts, and images are served instantly from disk cache rather than re-downloaded

Risk: Medium

stale content if cache-busting strategy is absent

How it works

  1. 01

    Server attaches cache headers to the response

    When a browser requests an asset, the server includes HTTP response headers like Cache-Control: max-age=31536000, public. These instruct the browser how long the resource can be stored locally without re-validating.

  2. 02

    Browser stores a local copy on first download

    The browser downloads the resource normally and stores a copy alongside its cache metadata (expiry time, ETag). This is transparent — no user action or client-side code required.

  3. 03

    On the next request, the browser checks freshness

    Before touching the network, the browser checks whether its cached copy is still within its max-age window. If the resource is fresh, the request never leaves the device.

  4. 04

    Fresh resources load from disk — no network round trip

    A cache hit loads from local disk in microseconds. No DNS lookup, no TCP connection, no data transfer. This directly reduces FCP and LCP for returning visitors, and eliminates bandwidth cost for assets that have not changed.

.htaccess (Apache)

Code
<IfModule mod_expires.c>
  ExpiresActive On

  # Images and fonts — rarely change, cache for 1 year
  ExpiresByType image/jpeg       "access plus 1 year"
  ExpiresByType image/png        "access plus 1 year"
  ExpiresByType image/webp       "access plus 1 year"
  ExpiresByType image/svg+xml    "access plus 1 year"
  ExpiresByType image/x-icon     "access plus 1 year"
  ExpiresByType application/font-woff2 "access plus 1 year"

  # CSS and JS — versioned by WordPress, 1 month is safe
  ExpiresByType text/css               "access plus 1 month"
  ExpiresByType application/javascript "access plus 1 month"
</IfModule>

<IfModule mod_headers.c>
  <filesMatch "\.(jpg|jpeg|png|gif|webp|svg|ico|woff2|woff|ttf)$">
    Header set Cache-Control "max-age=31536000, public, immutable"
  </filesMatch>
  <filesMatch "\.(css|js)$">
    Header set Cache-Control "max-age=2592000, public"
  </filesMatch>
</IfModule>

WordPress appends ?ver=x.x.x version strings to CSS and JS URLs, so updating a file changes its URL — making it safe to cache for longer periods without stale content risk.

Limitations

  • Browser caching only helps returning visitors. First-time visitors have an empty cache and must download every asset from the server. This technique does not improve initial load times.
  • Long cache durations mean users may continue seeing an outdated CSS or JS file after a deployment. WordPress handles this automatically for enqueued assets via version strings, but custom or hardcoded asset URLs require manual cache-busting.
  • HTML documents containing frequently changing data (live prices, personalised content, real-time feeds) should use very short cache durations or Cache-Control: no-store to prevent users from seeing stale HTML.

Common Mistakes

Caching HTML pages with long max-age values

HTML is the entry point for every page and often changes. Long HTML cache times cause users to see outdated layouts or content. Use short durations or no-cache with ETags for HTML, reserving long durations for static assets only.

Setting very short cache times for truly static assets

Images, fonts, and versioned CSS/JS files rarely change. Caching them for only a few hours negates most of the benefit. Assets with version strings in their URL can safely use 1-year durations.

Using Cache-Control: private when public is appropriate

private prevents CDNs and proxy caches from storing the response. For non-personalised static assets, use public so the full caching chain benefits — not just the individual browser.

Sending conflicting Expires and Cache-Control headers

When both are present, Cache-Control: max-age takes precedence in modern browsers. Use only Cache-Control as the authoritative directive and omit or align Expires to avoid confusion.

Applying the same cache duration to all resource types

Different asset types have very different change frequencies. Images and fonts can tolerate 1-year caches; JS and CSS depend on your deployment strategy; HTML should be short or conditional. Blanket caching rules are a blunt instrument.

Forgetting to implement cache busting when updating assets

When a cached file like main.js is updated on the server without a filename or query string change, browsers will serve the old version until it expires. WordPress handles this for enqueued scripts and styles — ensure custom includes do the same.

Test this techniqueCache AnalysisRun the tool

Need help applying this to your site?

Targeted fixes, not guesswork. We identify which techniques move the needle for your specific WordPress stack.

Start an Engagement
Support our family now