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 namedSESS
orSSESS
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.