• Blog
  • Projects
  • Resume
  • Hire Me
  • About
  • Contact
Menu

Code by Amir

Street Address
City, State, Zip
Phone Number
Amir Boroumand

Code by Amir

  • Blog
  • Projects
  • Resume
  • Hire Me
  • About
  • Contact

A Web Developer's Guide to Browser Caching

July 25, 2017 Amir Boroumand

Overview

Caching is a useful yet surprisingly complex feature of web browsers.

In this article, we'll explain how the browser uses its cache to load pages faster, which factors determine cache duration, and how we can bypass the cache when necessary.  

Why is Caching Important?

All browsers attempt to keep local copies of static assets in an effort to reduce page load times and minimize network traffic.

Fetching a resource over a network will always be slower than retrieving it from local cache.  This is true whether the server is on the same network or it's located on the far side of the world. 

How Browser Caching Works

Case 1: User has not visited the site before

The browser won't have any files cached for the site so it will fetch everything from the server.

Below is a snapshot of the resources downloaded when visiting the Wikipedia home page for the first time.   The status bar at the bottom shows that 265KB of data was transferred to the browser.

Case 2: User has visited the site before

The browser will retrieve the HTML page from the web server but consult its cache for the static assets (JavaScript, CSS, images).

We can see the difference cache makes when we refresh the Wikipedia page:

The data transferred went down to 928 bytes - that's 0.3% the size of the initial page load.  The Size column shows us that most of the content is pulled from cache.

Chrome will pull files from either memory cache or disk cache.  Since we didn't close our browser between Cases 1 & 2, the data was still in memory cache.  

Show the Browser Cache

In Chrome, we can go to chrome://cache to view the contents of the cache.  This will display a page of links to a detailed view for each cached file.
 chrome://cache

chrome://cache

How Does the Browser Know What to Cache?

The browser inspects the headers of the HTTP response generated by the web server.   There are four headers commonly used for caching:

  • ETag
  • Cache-Control
  • Expires
  • Last-Modified

ETag

The ETag (or Entity Tag) is a string that serves as a cache validation token.   This is usually a hash of the file contents.  

The server can include an ETag in its response, which the browser can then use this in a future request (after the file has expired) to determine if the cache contains a stale copy.  

If the hash is the same, then the resource hasn't changed and the server responds with a 304 response code (Not Modified) with an empty body.    This lets the browser know it's still safe to use the cached copy.

782-imported-1443570285-782-imported-1443554754-56-original.jpg

Note that ETag is only used in requests whenever the file has expired from cache.  

Cache-Control

The Cache-Control header has a number of directives we can set to control cache behavior, expiration, and validation.

Cache Behavior

public

public means that the resource can be cached by any cache (browser, CDN, etc)

private

private means that the resource can only be cached by the browser 

no-store

This tells the browser to always request the resource from the server 

no-cache

This one is actually a bit misleading.   It doesn't mean "do not cache".

This tells the browser to cache the file but not to use it until it checks with the server to validate we have the latest version.   This validation is done with the ETag header.  

This is commonly used with HTML files since it makes sense for the browser to always check for the latest markup.

    Expiration

    max-age=<integer>

    This specifies the length of time in seconds the resource should be cached.So a max-age=60 means that it should be cached for 1 minute. RFC 2616 recommends that the maximum value for should no longer than 1 year (max-age=31536000).

    s-max-age=<integer>

    This is only used by intermediate caches like a CDN.

    Validation

    must-revalidate

    This tells the cache it must verify the status of the stale resource before using it and expired ones should not be used.

    Expires

    The Expires header is from the older HTTP 1.0 days but is still used on many sites.

    This header field provides an expiration date after which the asset is considered invalid.  

    Expires: Wed, 25 Jul 2018 21:00:00 GMT
    The browser will ignore this field if there's a max-age directive in Cache-Control

    Last-Modified

    The Last-Modified header is also from the HTTP 1.0 days.   

    Last-Modified: Mon, 12 Dec 2016 14:45:00 GMT
    

    This field contains the date and time the resource was last modified. 

    HTML Meta Tags

    Prior to HTML5, using meta tags inside HTML to specify cache-control was a valid approach:

    <meta http-equiv="Cache-control" content="no-cache">

    Using a meta tag like this is now discouraged and is not valid HTML5.   Why?  It's not a good idea because only browsers will be able to parse the meta tag and understand it.   Intermediate caches won't.

    So always send caching instructions via HTTP headers.

    HTTP Response

    Let's take a look at an sample HTTP response:

    Accept-Ranges: bytes
    Cache-Control: max-age=3600
    Connection: Keep-Alive
    Content-Length: 4361
    Content-Type: image/png
    Date: Tue, 25 Jul 2017 17:26:16 GMT
    ETag: "1109-554221c5c8540"
    Expires: Tue, 25 Jul 2017 18:26:16 GMT
    Keep-Alive: timeout=5, max=93
    Last-Modified: Wed, 12 Jul 2017 17:26:05 GMT
    Server: Apache
    • Line 2 tells us that the max-age is 1 hour
    • Line 5 tells us that this is a PNG image
    • Line 7 shows us the ETag value which will be used for validation after the 1 hour mark to verify that the resource hasn't changed
    • Line 8 is the Expires header which will be ignored since max-age is set
    • Line 10 is the Last-Modified header which shows when the image was last modified

    Caching Pitfalls

    So we've established that browser caching is awesome, and we should take advantage of it.  

    But we also want users see the latest version of our page when we make updates.  We can't expect them to do a hard refresh (Ctrl-F5) every time they visit our site or clear their cache regularly. 

    These types of caching issues are often a source of frustration for both the developer and end-user.   A user may see a broken page or a button that behaves strangely because they have an outdated stylesheet or JavaScript code.

    Stale Files

    Below is a Twitter exchange between Chase Support and a user having issues with a login form on the banking site.   The user likely had some old JavaScript cached in their browser which caused the form to reset instead of submit when the Logon button was clicked.


    Let's explore another situation where stale files could bite us.  

    Suppose we fix a bug in a JavaScript file called app.min.js and push the update to our production site.  

    This is what our HTML looks like:

    <script src="assets/js/app.min.js">

    Our web server sets the max-age of JavaScript files to 1 week (604,800 seconds).

    Cache-Control: private, max-age=604800
    

    After the update, some users report they are still having issues symptomatic of the bug.

    What's going on here?

    • Bob visited the site 2 weeks ago and has a cached copy of buggy app.min.js.   Since his copy is older than max-age, the browser will retrieve the file from the server, and he gets the latest bug-free version.
    • Mary visited the site 2 days ago and also has a cached copy of buggy app.min.js.  Her copy is newer than max-age so her browser is still happily using the cached copy.

    In the next section, we'll see how to prevent these issues with a technique called cache busting.

    Cache Busting

    Cache busting is where we invalidate a cached file and force the browser to retrieve the file from the server.   

    We can instruct the browser to bypass the cache by simply changing the filename.   To the browser, this is a completely new resource so it will fetch the resource from the server.  

    Cache busting also allows us to keep long max-age values for resources that may change frequently.   Google recommends that max-age be set to 1 year (source).

    Versioning

    We could add a version number to the filename:

    assets/js/app-v2.min.js

    Fingerprinting

    We could add a fingerprint based on the file contents:

    assets/js/app-d41d8cd98f00b204e9800998ecf8427e.min.js

    Append Query String

    We could append a query string to the end of the filename:

    assets/js/app.min.js?version=2

    The query string approach has known issues with proxy servers so this method is generally discouraged.

    Best Practices

    Do

    • Use the Cache-Control and ETag headers to control cache behavior for static assets
    • Set long max-age values to reap the benefits of browser cache
    • Use fingerprinting or versioning for cache busting

    Don't

    • Use HTML meta tags to specify cache behavior
    • Use query strings for cache busting

    FAQ

    How can I tell if a file was loaded from cache?

    Check out the Developer Tools in your browser.   In Chrome, this information is shown in the Network tab under the Size column.

    How do I prevent caching for a file?

    Use the following response header:

    Cache-Control: no-cache, no-store, must-revalidate
    In front-end
    ← Never Trust Data from the BrowserWeb Storage API: Local Storage & Session Storage →

    Archive

    • March 2018
      • Mar 15, 2018 Spring Boot Essentials: Change the Context Path Mar 15, 2018
      • Mar 14, 2018 Spring MVC Essentials: @RequestMapping and @PathVariable Annotations Mar 14, 2018
      • Mar 1, 2018 Eclipse IDE Tip: Multiple Search Tabs Mar 1, 2018
    • February 2018
      • Feb 20, 2018 Java Essentials: Understanding String Equality Feb 20, 2018
      • Feb 12, 2018 My Journey from Sysadmin to Developer Feb 12, 2018
    • January 2018
      • Jan 23, 2018 Java Essentials: Preventing ConcurrentModificationException Jan 23, 2018
    • December 2017
      • Dec 31, 2017 Java Developer's Guide to SSL Certificates Dec 31, 2017
      • Dec 7, 2017 Java Essentials: Use Integer.parseInt() to convert String to Integer Dec 7, 2017
    • November 2017
      • Nov 30, 2017 Configure Tomcat Logging Behind a Load Balancer Nov 30, 2017
      • Nov 25, 2017 Remote Debugging SAP Hybris using Eclipse Nov 25, 2017
      • Nov 5, 2017 Using Welcome Files in Apache Tomcat Nov 5, 2017
      • Nov 1, 2017 Java Servlet Essentials: sendRedirect vs forward Nov 1, 2017
    • October 2017
      • Oct 26, 2017 Refactoring JSP Scriptlets using JSTL and MVC Architecture Oct 26, 2017
      • Oct 9, 2017 Spring Framework Essentials: Bean Scopes Part 1 Oct 9, 2017
      • Oct 2, 2017 Java Essentials: The Default Constructor Oct 2, 2017
    • September 2017
      • Sep 26, 2017 When to Use an Abstract Class in Java Sep 26, 2017
      • Sep 20, 2017 Populate a Select Dropdown List using JSON Sep 20, 2017
      • Sep 13, 2017 Apache Struts Vulnerability Exploited in Equifax Breach (CVE-2017-5638) Sep 13, 2017
    • August 2017
      • Aug 31, 2017 Stop Returning Null in Java Aug 31, 2017
      • Aug 16, 2017 View Unconfirmed Subscribers with the MailChimp API Aug 16, 2017
      • Aug 10, 2017 Pushing Price Alert Notifications with AWS Lambda, SNS, and Node.js Aug 10, 2017
      • Aug 1, 2017 Use Duplicate Keys in a Map with Multimap from Google Guava Aug 1, 2017
    • July 2017
      • Jul 31, 2017 Never Trust Data from the Browser Jul 31, 2017
      • Jul 25, 2017 A Web Developer's Guide to Browser Caching Jul 25, 2017
      • Jul 20, 2017 Web Storage API: Local Storage & Session Storage Jul 20, 2017
      • Jul 16, 2017 Creating a Dynamic Fixed Header on a Scroll Event Jul 16, 2017
      • Jul 7, 2017 Suppressing console.log() messages in production Jul 7, 2017
    • June 2017
      • Jun 28, 2017 Java Map Fundamentals Jun 28, 2017
      • Jun 21, 2017 Software Anti-Patterns: Magic Numbers Jun 21, 2017
      • Jun 12, 2017 Curbing Complexity in Software Jun 12, 2017
      • Jun 7, 2017 View Source vs. Inspect Element Jun 7, 2017
      • Jun 5, 2017 Work Faster in Spring Boot with DevTools Jun 5, 2017
    • May 2017
      • May 31, 2017 User Account Registration feature with Spring Boot May 31, 2017
      • May 4, 2017 Forgot Password feature with Java and Spring Boot May 4, 2017
    • April 2017
      • Apr 29, 2017 MCSD: App Builder Certification Path: Microsoft 70-480 Exam Apr 29, 2017
      • Apr 27, 2017 A Developer's Guide to Accepting Online Payments with Stripe Apr 27, 2017
      • Apr 23, 2017 Create Excel Files in C# Apr 23, 2017
      • Apr 7, 2017 Bootstrap Fundamentals - Layout Apr 7, 2017
    • March 2017
      • Mar 30, 2017 Parse Command-Line Arguments Using Getopt Mar 30, 2017
      • Mar 18, 2017 Using Apache as a Reverse Proxy for Spring Boot Embedded Tomcat Mar 18, 2017
      • Mar 14, 2017 Check for Logged in User with Thymeleaf and Spring Security 4 Mar 14, 2017
      • Mar 12, 2017 Create a REST API with Spring Boot Mar 12, 2017
      • Mar 6, 2017 The Rise of Microservices: Smashing the Monolith Mar 6, 2017
    • February 2017
      • Feb 17, 2017 Using Vagrant for Consistent Development Environments Feb 17, 2017
      • Feb 13, 2017 Automated EBS Snapshots using AWS Lambda & CloudWatch Feb 13, 2017
      • Feb 2, 2017 MySQL Database Indexing for Developers Feb 2, 2017
    • January 2017
      • Jan 14, 2017 AWS Certified Developer Exam Jan 14, 2017
      • Jan 3, 2017 Why Version Control Matters Jan 3, 2017
    • December 2016
      • Dec 19, 2016 Discovering JShell in JDK 9 Dec 19, 2016
      • Dec 10, 2016 Remove Unused CSS Rules using Chrome Audit Panel Dec 10, 2016
      • Dec 7, 2016 Getting Started with Grunt Dec 7, 2016
      • Dec 4, 2016 HTTP/2 - The Sequel Dec 4, 2016
    • November 2016
      • Nov 7, 2016 Cache Me If You Can Nov 7, 2016
    • October 2016
      • Oct 26, 2016 How Algorithms Shape Our World Oct 26, 2016
      • Oct 13, 2016 Let's Talk about HTTP Cookies Oct 13, 2016
    • September 2016
      • Sep 9, 2016 Understanding Optional Types in Swift Sep 9, 2016
    • August 2016
      • Aug 29, 2016 libc++abi.dylib: terminating with uncaught exception of type NSException Aug 29, 2016
      • Aug 25, 2016 Finalize Methods are a Bad Idea Aug 25, 2016
      • Aug 1, 2016 Why doesn't a static method call on null object reference generate a NullPointerException? Aug 1, 2016
    • July 2016
      • Jul 19, 2016 Use Character Arrays to Store Sensitive Text Data in Java Jul 19, 2016
      • Jul 13, 2016 10 Books Every Software Developer Should Read Jul 13, 2016
      • Jul 8, 2016 Pitfalls of import wildcards in Java Jul 8, 2016
      • Jul 1, 2016 Interfaces in Object-Oriented Design Jul 1, 2016
    • June 2016
      • Jun 24, 2016 Technical Debt in Software Design Jun 24, 2016
    • May 2016
      • May 15, 2016 Converting an Object to JSON in PHP May 15, 2016
      • May 10, 2016 Using Callbacks with Net::SFTP::Foreign in Perl May 10, 2016
      • May 9, 2016 IKEA stock notifications using PHP and XML May 9, 2016
      • May 2, 2016 Removing empty array elements in PHP May 2, 2016
    • April 2016
      • Apr 15, 2016 JavaScript Design Pattern - IIFE Apr 15, 2016
      • Apr 1, 2016 Hardening SSL in Apache 2.x Apr 1, 2016
    • March 2016
      • Mar 30, 2016 The Single Page Application (SPA) Model Mar 30, 2016
      • Mar 27, 2016 Understanding & Preventing CSRF Attacks Mar 27, 2016
      • Mar 20, 2016 SMS Integration with Twilio in PHP Mar 20, 2016
      • Mar 18, 2016 Root Password Rotation using the Red Hat Satellite 5 API Mar 18, 2016
      • Mar 11, 2016 Using PDO for Database Access in PHP Mar 11, 2016
      • Mar 4, 2016 Getting Started with AWS SDK for PHP Mar 4, 2016
    • February 2016
      • Feb 26, 2016 PHP Debugging - print_r vs var_dump vs var_export Feb 26, 2016
      • Feb 19, 2016 Using Moose for Perl Objects Feb 19, 2016
      • Feb 12, 2016 Static vs Dynamic Typing Feb 12, 2016
      • Feb 5, 2016 JavaScript Execution Contexts Feb 5, 2016
    • January 2016
      • Jan 29, 2016 CSS Specificity Jan 29, 2016
      • Jan 22, 2016 Perl SFTP File Listing Jan 22, 2016
      • Jan 15, 2016 JavaScript Loop Caching Jan 15, 2016
      • Jan 8, 2016 Why Inline CSS Sucks Jan 8, 2016
    Summary Block
    This is example content. Double-click here and select a page to feature its content. Learn more
    Featured
    Latest Article

    Subscribe

    Sign up with your email address to receive news and updates.

    Thank you!