How to configure caching for static assets

How to configure caching for static assets

Goal

Caching is a common and effective way to improve the performance of a website. Both Platform.sh and your browser can cache static files with the proper cache header configuration. This guide will explain how to configure those headers.

Assumptions

  • This guide applies only to static assets. For responses generated dynamically by the application, the application itself will need to include the appropriate cache headers in the response.
  • Some static assets may be inappropriate to cache. This guide assumes that most or all files are safe to cache.
  • Depending on the use case, it may make sense to cache files for a relatively short period (minutes) or a very long period (weeks).
  • This guide applies equally to static files included with your application as well as those produced by your application or uploaded by a user.

Problems

If a file is cached and then updated, anywhere the old file is already cached will still keep the old copy until its cache expires. For that reason it is usually better to err on the side of shorter cache lifetimes if unsure.

Steps

1. Determine which files to cache

Different cache settings may be set for different files, based on either their directory or precise filename. For this example assume the following directory structure:

/
  .platform/
  .platform.app.yaml
  web/
    index.php
    robots.txt
    uploads/
    assets/
    generated/
    nocache/

Where the document root is web.

  • uploads contains user-uploaded files that should be cached for 10 minutes.
  • assets contains developer-provided files that may be cached for longer, such a 1 day.
  • robots.txt is a miscellaneous file that is safe to cache.
  • generated contains application-generated files, such as CSS or JS files, that may be cached for a long period but need to change quickly when code changes.
  • nocache contains files that change so rapidly that caching them is not useful.

2. Set a default cache lifetime for all static files

Locate the web.locations block in .platform.app.yaml for the document root. In this example it would look something like:

web:
    locations:
        '/':
            root: 'web'

As a sibling of root, add an expires key with a time period. To set an expiration time of 5 minutes, use:

web:
    locations:
        '/':
            root: 'web'
            expires: 5m

The time period can be in “s” (seconds), “m” (minutes), “h” (hours), “d” (days), “w” (weeks), “M” (30 day months), or “y” (365 day years).

This configuration will automatically set a cache-control header for all static files to cache the file for 5 minutes.

If you have multiple paths mapped under web.locations, each one may have its own expires declaration.

See the web configuration documentation for more details.

3. Configure directory-specific alternate cache lifetimes

To change the cache information (or any other configuration) for a specific sub-path within the docroot, use the rules block:

web:
    locations:
        '/':
            root: 'web'
            expires: 5m
            rules:
                '^/uploads':
                    expires: 10m
                '^/assets':
                    expires: 1d
                '^/nocache':
                    expires: -1

Each rules block is a regular expression that may match the path. In this example:

  • any URL that begins with /uploads will have a cache lifetime of 10 minutes
  • any URL that begins with /assets will have a cache lifetime of 1 day
  • any URL that begins with /nocache will explicitly have caching disabled
  • and any other static files will have a cache lifetime of 5 minutes

The rules block also makes it possible to target specific files by name or pattern.

4. Configure cache-busting URLs

A common pattern is for certain files to have very long cache lifetimes but need to be cleared on-demand after a code deploy. That’s especially true for aggregated CSS or JS files. The standard technique for that is to include a “cache busting” query on the URL as produced by the application, which since it’s a different URL will not use the previously cached value. There are two steps to configure that pattern.

First, set an especially long cache lifetime for the files in question.

web:
    locations:
        '/':
            root: 'web'
            expires: 5m
            rules:
                '^/generated/*.(css|js)':
                    expires: 2w

That configuration will set a 2 week cache lifetime for any files under the generated directory that end in .css or .js.

Second, when your application generates URLs to those files it needs to include a ?cache=somestring query parameter. When the application code changes then somestring will need to change, too. An easy way to do that is to base the string on the PLATFORM_TREE_ID environment variable, which will change if and only if the code in the application changed.

For example, in PHP one could do:

$somestring = substr(getenv('PLATFORM_TREE_ID'), 0, 6);

That will produce a junk string that is the first 6 characters of the tree ID. When new code is deployed, PLATFORM_TREE_ID will change and thus $somestring will change as well. The application will then need to generate URLs that include that value, such as:

https://www.example.com/generated/forum.css?cache=$somestring

5. Enable router caching

In .platform/routes.yaml, ensure that caching is enabled for the appropriate route:

"https://{default}/":
    type: upstream
    upstream: "app:http"
    cache:
        enabled: true
        cookies: ['/^SS?ESS/']
        default_ttl: 5m
  • The enabled statement turns on the cache, causing the router to cache any responses that have a proper cache header (both static and application-generated responses).
  • The cookies statement is an array of cookie name regular expressions that should be excluded when calculating the cache. Generally it should be set to whatever your application’s cookie name is. In this example, cookies named SESS or SSESS will be excluded from the cache. If this setting is incorrect then any request with an active session will not be cached, even if it’s safe to do so.
  • The default_ttl statement will set a cache header of 5 minutes on all static responses that do not already have one. In the example above all static files will have a cache header already so it has no effect.

See the routing documentation for more details.

Conclusion

All static files will now be cached for the specified period of time in the router and in each visitor’s browser cache. Dynamic application responses that have a cache header set by the application will be cached as well. Dynamic application responses that have no cache-header will not be cached at any level.