This site uses cookies for analytics. By continuing to browse this site, you agree to this use.
A JS Foundation Project

HTTP Cache

HTTP Cache (@sonarwhal/rule-http-cache)

http-cache verifies that the page and all its resources follow a good, sustainable caching strategy.

Why is this important?

The right caching strategy can help improve site performance through:

  • Shorter load times
  • Reduced bandwidth
  • Reduced server costs
  • Having predictable behavior across browsers

Currently about ~50% of resources on the web can’t be cached due to their configuration:

Cacheable Resources

Source: http archive

What does the rule check?

This rule checks the configuration of the cache-control header to validate that the page and resources have a good caching strategy:

  • all requests have a Cache-Control header, otherwise the behavior can change from browser to browser
  • main page should have a short cache (<= 3 minutes) or not cache at all
  • static resources such as JavaScript, CSS, images, etc.:
    • have a long expiry value (>= 1 year)
    • use the immutable directive
    • follow some kind of filename/path based revving, and not one based on query string parameters (see: problems with proxys)

The built-in regular expressions for file revving are:

/\/[^/]+[._-]v?\d+(\.\d+(\.\d+)?)?[^/]*\.\w+$/i
/\/v?\d+\.\d+\.\d+.*?\//i
/\/v\d.*?\//i
/\/([^/]+[._-])?([0-9a-f]{5,})([._-].*?)?\.\w+$/i

This will match URLs like the following:

https://example.com/assets/jquery-2.1.1.js
https://example.com/assets/jquery-2.1.1.min.js
https://example.com/assets/jquery-3.0.0-beta.js
https://example.com/assets/favicon.123.ico
https://example.com/wp-content/uploads/fvm/out/header-cb050ccd-1524626949.min.js
https://cdn.example.com/jquery.lazy/1.6.5/jquery.lazy.min.js
https://example.com/site/javascript/v5/jquery.cookie.js
https://static.xx.fbcdn.net/rsrc.php/v3iJhv4/yG/l/en_US/sqNNamBywvN.js
https://example.com/assets/unicorn-d41d8cd98f.css
https://example.com/assets/app.e1c7a.bundle.js
https://example.com/assets/9f61f58dd1cc3bb82182.bundle.js
https://example.com/assets/9f61f.js
https://example.com/assets/9f61f.min.js

Test your URLs

Examples that trigger the rule

Cache-Control header is not sent:

HTTP/... 200 OK

Content-Type: text/javascript; charset=utf-8
...

An invalid directive:

HTTP/... 200 OK

Cache-Control: invalid directive
...

An invalid directive-value pair:

HTTP/... 200 OK

Cache-Control: max-age=abc
...

Uses a directive that is not recommended:

HTTP/... 200 OK

Cache-Control: max-age=3600, must-revalidate
...

The combination of directives doesn’t make sense:

HTTP/... 200 OK

Cache-Control: no-cache, max-age=3600
...

The page has a max-age value greater than 3 minutes

HTTP/... 200 OK

Content-Type: text/html; charset=utf-8
Cache-Control: max-age=300
...

A static resource has a max-age value less than 1 year:

HTTP/... 200 OK

Content-Type: text/javascript; charset=utf-8
Cache-Control: max-age=3600
...

A static resource doesn’t have the immutable directive:

HTTP/... 200 OK

Content-Type: text/javascript; charset=utf-8
Cache-Control: max-age=31536000
...

Examples that pass the rule

A static resource with max-age greater than 1 year and the immutable directive:

HTTP/... 200 OK

Content-Type: text/javascript; charset=utf-8
Cache-Control: max-age=31536000, immutable
...

A page with no-cache :

HTTP/... 200 OK

Content-Type: text/html; charset=utf-8
Cache-Control: no-cache
...

How to configure the server to pass this rule

How to configure Apache

Enabling Apache to automatically add the Cache-Control header (as well as the equivalent Expires header) can be done using the ExpiresActive directive.

Cache-Control header’s max-age values can be set using the ExpiresDefault and ExpiresByType directives. Other values such as immutable can be set using the Header directive.

If you don’t want to start from scratch, below is a generic starter snippet that contains the necessary configurations to ensure that commonly used file types are served with the appropriate Cache-Control header, and thus, make your web site/app pass this rule.

Important notes:

  • Do not use the following snippet if you are not doing filename revving.
  • The following relies on Apache being configured to have the correct filename extensions to media types mappings (see Apache section from content-type rule).
<IfModule mod_expires.c>

# Automatically add the `Cache-Control` header (as well as the
# equivalent `Expires` header).

ExpiresActive on

# By default, inform user agents to cache all resources for 1 year.

ExpiresDefault "access plus 1 year"


# Overwrite the previous for file types whose content usually changes
# very often, and thus, should not be cached for such a long period,
# or at all.

# AppCache manifest files

ExpiresByType text/cache-manifest "access plus 0 seconds"


# /favicon.ico (cannot be renamed!)

# [!] If you have access to the main Apache configuration
# file, you can match the root favicon exactly using the
# `<Location>` directive. The same cannot be done inside
# of a `.htaccess` file where only the `<Files>` directive
# can be used, reason why the best that can be done is match
# all files named `favicon.ico` (but that should work fine
# if filename/path based revving is used)
#
# See also: https://httpd.apache.org/docs/current/sections.html#file-and-web.

<Files "favicon.ico">
ExpiresByType image/x-icon "access plus 1 hour"
</Files>


# Data interchange

ExpiresByType application/atom+xml "access plus 1 hour"
ExpiresByType application/rdf+xml "access plus 1 hour"
ExpiresByType application/rss+xml "access plus 1 hour"

ExpiresByType application/json "access plus 0 seconds"
ExpiresByType application/ld+json "access plus 0 seconds"
ExpiresByType application/schema+json "access plus 0 seconds"
ExpiresByType application/vnd.geo+json "access plus 0 seconds"
ExpiresByType text/xml "access plus 0 seconds"


# HTML

ExpiresByType text/html "access plus 0 seconds"


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# Where needed add `immutable` value to the `Cache-Control` header

<IfModule mod_headers.c>

# Because `mod_headers` cannot match based on the content-type,
# the following workaround needs to be done.

# 1) Add the `immutable` value to the `Cache-Control` header
# to all resources.

Header merge Cache-Control immutable

# 2) Remove the value for all resources that shouldn't be have it.

<FilesMatch "\.(appcache|cur|geojson|ico|json(ld)?|x?html?|topojson|xml)$">
Header edit Cache-Control immutable ""
</FilesMatch>

</IfModule>

</IfModule>

Also note that:

  • The above snippet works with Apache v2.2.0+, but you need to have mod_expires and mod_headers enabled in order for it to take effect.

  • If you have access to the main Apache configuration file (usually called httpd.conf), you should add the logic in, for example, a <Directory> section in that file. This is usually the recommended way as using .htaccess files slows down Apache!

    If you don’t have access to the main configuration file (quite common with hosting services), add the snippets in a .htaccess file in the root of the web site/app.

How to configure IIS

You can enable the Cache-Control and/or Expire headers on IIS using the <clientCache> element under <staticContent>.

<clientCache> will set the cache for all the configured static content so you might want to use it in combination with the <location> element and set different values depending on where the resources are in the file system.

The following is an example that sets cache-control: no-cache for all static resources and then overrides it for the files under the static folder with cache-control: max-age=31536000, immutable:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<staticContent>
<clientCache cacheControlMode="DisableCache" />
</staticContent>
</system.webServer>
<location path="static">
<system.webServer>
<staticContent>
<clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="365.00:00:00" cacheControlCustom="immutable" />
</staticContent>
</system.webServer>
</location>
</configuration>

In the example above you want to have your JavaScript, CSS, images, etc. under the static folder, and your HTML elesewhere. If your static content is in another folder change the path of <location path="static"> to the right one.

Important notes:

  • Do not use the above snippet if you are not doing filename revving.
  • The above snippet works with IIS 7+.
  • You should use the above snippet in the web.config of your application.

Can the rule be configured?

Yes, you can configure:

  • the max-age values for the page and resources
  • the regular expressions used to know if the file is immutable or not

max-age

By default the recommended value for the page is Cache-Control: no-cache or a max-age equal or less to 3 minutes. For the resources max-age should be greater or equal to 1 year. You can change this as follows:

"http-cache": ["error", {
"maxAgeTarget": 300, // 5 minutes in seconds
"maxAgeResource": 1576800 // 6 months in seconds
}]

Custom regular expressions for revving files

If none on the built-in regular expressions work for your use case, you can provide your own via the revvingPatterns property. This property accepts an Array of escaped RegExp:

in then .sonarwhalrc file:


{
"connector": {...},
"formatters": [...],
"rules": {
"http-cache": ["error", {
"revvingPatterns": ["\\/\\d+\\/\\w+\\.\\w{1,3}"]
}],
...
},
...
}

Also pay attention to the escaping. The example above will validate that static resources follow a convention similar to the following one:

https://example.com/assets/12345/script.js
https://example.com/assets/12345/styles.css

How to use this rule?

To use it you will have to install it via npm:

npm install @sonarwhal/rule-http-cache

Note: You can make npm install it as a devDependency using the --save-dev parameter, or to install it globally, you can use the -g parameter. For other options see npm's documentation.

And then activate it via the .sonarwhalrc configuration file:

{
"connector": {...},
"formatters": [...],
"parsers": [...],
"rules": {
"http-cache": "error"
},
...
}

Further Reading