The first layer of performance onion is about asset delivery and caching. Each asset, which you do not have to deliver twice, makes your website faster. This blogpost shows, what you can do in this area, without touching the content of your website.
The main secret for this layer is called “Caching”. Caching is used in two areas: Browser and proxy caching. Browser caching is probably well known by most of us. This is, when browsers save assets for multiple usage.
Proxies are services, which cache assets and complete pages and deliver them to the browsers as if they would be delivered from the original server. There are (at least) two kinds of proxies in the world: Forward and reverse proxies.
Forward proxies are installed on the client side of the web. They normally act as a gateway between the “bad” internet and the “good” intranet. Another reason for such a proxy may be to save bandwidth.
Reverse proxies act on the server side of the request. The goal is to keep traffic away from the CMS and so reduce the server load. An open source player in this area is Varnish Cache. The last chapter of this series will dig a little bit deeper into varnish.
Caching is a good thing, but can be dangerous, too. Why? Because there is software (browser and proxies) between your client and his users, which you can not control. It is not possible to tell, the admins and persons behind the software to clear the caches manually.
This is where cache invalidation comes in. You need to use techniques, which tell the browsers and the proxies when to use the asset from the cache and when to request or revalidate the asset (again). This is done by setting HTTP headers in the server response correctly.
HTTP Headers
The HTTP headers can be grouped into three categories: Allowance headers (1) define whether an asset or page may be cached or not. Cache – Matching (2) headers identify the assets, which are cached and can be delivered to the client. The Freshness (3) headers are there to check, whether the assets are expired or not.
Cache-Control-Header (1)
The Cache-Control header field specifies directives, which are honored by all caching mechanisms. Several directives can be set in this header. Important values are public, no-store, no-cache and max-age.
Most of them are self-explanatory. But the “no-cache” directive means, that the asset may be cached, but not be delivered to the client without revalidating the asset at the origin server. The setting of max-age sets the maximum of time when the asset must be revalidated.
The “max-age” parameter is quite similar to the “Expires” header (see below), as both determine the time when an asset must be delivered again. From that what I have read, the Cache-Control-Header always overrules the Expires header.
The MDN provides a good overview over all the possible options: https://developer.mozilla.org/de/docs/Web/HTTP/Headers/Cache-Control
Pragma (1)
The pragma header is a legacy header and had a similar function as the Cache-Control Header. It should not be used any more, but you can set it to comply to very old caching systems.
Vary (2)
The Vary header is there to distinguish between different version for the same asset. Example is whether an asset can be delivered gzip compressed or not. Another one is the value “Accept-Encoding”. If you want to use this header, you need to be sure that it does not increase the cache too much and thus reduce the efficiency.
ETag (2)
Etags are another way to identify the uniqueness of an asset. The Etag header is created usually by the webserver. The creation of such an ETag is not trivial and may lead to duplicate cache entries, even it is the same asset. A common scenario for this “risk” is, if the inode on the disk of a file is used as the ETag value. If you have only on filesystem, that’s ok, but if the file systems are behind a load balancer and the files reside on different filesystems, the inodes can be different for the same file. This will lead to two or more cache entries.
Due to the complex setup and testing, you should really consider, whether it is necessary to used it, or whether the other mechanisms are sufficient or not.
If-None-Match (2)
This header is a header used to match assets against the ETag header. The cache this header to the server. The answer will be a “304 – Not modified”, if the asset is still there with the Etag. If the Etag does not exist any more, the client will receive the new file.
Last-Modified (2)
The Last-Modified header contains a timestamp, when the asset was last modified. It is useful together with the header “If-Modified-Since”. It serves as a comparison value for the caches.
If-Modified-Since (2)
This is the header, which is sent by the client to check, whether an asset was modified since a certain timestamp. If it was modified, the new asset is sent to the client. The timestamp for this comparison is set by the Last-Modified header.
Expires (3)
The Expires header is there to stale assets on client side. If the timestamp of an asset is reached, is will be requested from the server again. This setting should not be too far away in the future.
Implementation in TYPO3
Default htaccess
TYPO3 comes with a htaccess template file, which has reasonable defaults for the expires header. Due the discussions around the ETags, these are disabled by default at the end of the htaccess file.
Versioned file names
The problem with invalidating a cache is mostly “gone”, if you include a version number in the filename. An example: A simple “basic.css” will be cached, until a expires or cache control header forces a reload of the the file. That’s really bad, if you want to update your css file within the caching period.
TYPO3 is capable to include a version number in filenames of files, which are created within TYPO3. You can activate this in the InstallTool in the section “Frontend”. This works only correctly if you activate a rewrite rule in the htaccess file of your web server. It is included in the TYPO3 htaccess template too.
Cache-Control-Headers
TYPO3 is able to send cache control headers. You can activate it in TypoScript using the setting config.sendCacheHeaders = 1
The values are based on the internal cache settings of TYPO3. For the details please have a look at the TypoScript documentation.
Custom HTTP header
If this is not enough and you like to set individual headers, the TypoScript config config.additionalHeaders
is your friend. Within this numerical TypoScript array, you can set any header you want to, including the above mentioned headers.
Development tools
All popular browsers like Firefox or Chrome come with development tools, which reveal all request and response headers. The screen shot is from Google Chrome.
You find this view in the Chrome Dev Tools. Open them, click on the tab “Network” (1.). If the list of files is empty, you must reload the current page. In a second step, click on a transferred asset. Finally you will find the headers of this asset in the tab “Headers”.
If you prefer specialized desktop tools, you can check out the following:
- Insomnia (multi platform)
- I’m only resting (Windows only)
- Http-Client (MacOS only)
Conclusion
This was the second post about TYPO3 performance. It covered the caching on the browser and proxy side and how to influence it.
The next post will be about optimizing the frontend code, in oder to shrink the transferred file size.
In case you are interested in the other articles of this series, here is an overview of all already published posts:
- Layer 4 – Extension Development
- Layer 3 – Caching, TypoScript and more
- Layer 2 – Frontend Optimisation
- Layer 1 – Asset Delivery and Caching
- Introduction – Tools and Overview
Credits
This post is (partially) based on my talk at the TYPO3 University Days, which a prepared for my company in2code. Thanks for the idea and the time to prepare the talk.
I want to thank my supporters via patreon.com, who make this blog post possible:
If you appreciate my blog and want to support me, you can say “Thank You!”. Find out the possiblities here:
I found the blog post image on pixabay . It was published by jingoba under the CC0 public domain license. It was modified by myself using pablo on buffer.