How to make your Silverstripe website lightning fast

Publication
Author
by Florian Thoma
Reading time
Service
Silverstripe Development
Service
Performance Optimisation
lightning strike on dark night sky

Page speed is one of the factors that Google uses to rank websites in their organic search results. This is for good reason as UX studies found that page speed has a huge impact on user engagement. Optimizely published a  study where they found that even a delay in page load time of only 4 seconds reduces the number of page views by 11%.

Whether you measure how long it takes to fully load a page ("Page Load Time"), when the most important content loads ("Largest Contentful Paint"), or how long it takes for the web server to respond ("Time To First Byte"), it doesn't matter. The faster your website loads, the better. This blog post mostly covers the time to first byte, with a few exceptions.

What makes a website slow?

When a request for a website is submitted in a browser, a lot is happening. First, the DNS of the domain name requested needs to be resolved. This can take anything between 10 and a few hundred milliseconds. With the IP received from the DNS the web server can be contacted and the HTML document requested. The webserver then builds that HTML page, which usually takes around 300ms on an average Silverstripe installation. Then, that HTML document is sent back to the browser. The time it takes for that first byte of the HTML document to reach the browser is called "Time To First Byte". Google recommends a time to first byte (or "server response time") of 800ms or less. For a PHP application, that's really fast and can't be achieved by just putting Silverstripe on shared hosting without optimisation.

Once the browser receives that document, it will be parsed. All the linked resources like CSS and JavaScript files, as well as images, etc., will then be requested and downloaded. This again takes time. At the time of this writing, the average website takes about 2.5 seconds on desktop and 8.6 seconds on mobile to fully load.

What can be done to make Silverstripe fast?

There are a few things that can be done to make your Silverstripe site faster. 

Move away from shared hosting

Shared hosting usually doesn't have the power to cope with a high-performing Silverstripe site, or any CMS. The shared hosting providers have hundreds of sites on a single machine, which leaves very few resources for a single site. This will impact the performance of all sites when traffic to only one of them increases.

Moving to a dedicated VPS will improve the performance of your site significantly. We usually use Vultr servers for our customers and can highly recommend them.

Another option is to use the Silverstripe Platform, which gives you not only hosting on AWS, but also 24/7 support, code management, excellent deployment tools, monitoring and more.

Opcode Cache

One of the drawbacks (and advantage at the same time) of PHP is that the PHP code is only translated into machine-readable code when the code is executed. The advantage of this is that you don't have to compile the code for deployment like with other programming languages such as Java or Go. But it takes time for the PHP code to be compiled into opcode and the HTML to be generated.

An opcode cache keeps the generated opcode in memory or the file system so the files only need to be re-parsed when they change. By default, it is also used for object caching by Silverstripe core for things like the class manifest and YAML config.

Profiling

To find bottlenecks in your code, you can use profiling of the requests. There are two options I know of:  XHProf and  XDebug. By analysing the profiling data, you can see what's slowing down requests and where to focus your efforts.

Add caching

There are a few options that can be explored to cache some or all of the generated HTML.

Silverstripe partial caching

Partial caching is a great option for parts of the page that don't change very often, but still need a lot of database queries to build. A good example of this is the navigation, where a lot of pages need to be retrieved from the database for each request. Also, listings of other DataObjects can use a lot of database requests.

You can wrap sections of your layout or even the complete layout template in a <% cached %> tag and use aggregates of the elements included as a cache key. For a global navigation section, the cache key would look something like this:

<% cached
    'navigation',
    $List('SilverStripe\CMS\Model\SiteTree').max('LastEdited'),
    $List('SilverStripe\CMS\Model\SiteTree').count()
%>

This will reduce the database queries used to build your pages by up to 80%.

You'll find the  documentation on partial caching in the Silverstripe docs.

Object caching

Object caching gives you the ability to cache arbitrary data in the Silverstripe cache. It uses the PSR-16 standard "SimpleCache", and it is used by Silverstripe core to cache, for example, the class manifest, the YAML config, as well as the YAML translations. You can use this too, for example to cache data retrieved from a remote API or complex calculations.

Have a look at the documentation on object caching in the Silverstripe docs.

Static publishing

For sites that rarely change, you can use  static publishing. This saves the generated HTML of your pages in the file system, and requests bypass Silverstripe completely, resulting in a very fast site.

The drawback of this is that you can't have any interactive elements on your site, such as forms, on your site for static publishing to work, because there is no interaction with Silverstripe to submit and process your form data. If that's the case, partial caching (see above) is more suitable.

Browser caching with .htaccess

You can improve page load times for returning visitors by telling browsers to cache resources like images and scripts. This is not Silverstripe specific and can help with any website setup. If you're using Apache, you can add something like the following to your .htaccess file:

<ifModule mod_headers.c>
    # media
    <filesMatch "\.(avi|asf|au|bmp|flv|gif|ico|jpeg|jpg|m4a|m4v|mid|midi|mkv|mov|mp3|mp4|mpa|mpeg|mpg|ogg|ogv|pcx|png|ra|ram|rm|tif|tiff|wav|webm|webp|wma|wmv|avif|svg)$">
        Header set Cache-Control "max-age=31536000"
        Header unset Pragma
        Header unset Expires
    </filesMatch>
    # documents and archives
    <filesMatch "\.(aace|arc|arj|brf|bz2|cab|cda|csv|dmg|doc|docx|dotx|gz|hqx|kml|pages|pdf|pps|ppt|pptx|potx|rtf|sit|sitx|tar|tgz|txt|xls|xlsx|xltx|zip|zipx)$">
        Header set Cache-Control "max-age=86400"
        Header unset Pragma
        Header unset Expires
    </filesMatch>
    # css/js, fonts
    <filesMatch "\.(css|js|woff|woff2)$">
        Header set Cache-Control "max-age=31536000,immutable"
        Header unset Pragma
        Header unset Expires
        Header unset Last-Modified
        Header unset Etag
    </filesMatch>
</ifModule>

In this example, I split the caching into three groups: media, documents and archives, as well as code and fonts.

For media, I assume that once they are saved in the CMS, they are not going to be updated or changed. I set the Cache-Control header to a max-age of 31.5 million seconds, or one year. I also remove legacy caching headers, like Pragma and Expires, because they are obsolete if a correct Cache-Control header is set.

For documents and archives, I assume that they might get updated by the website authors sometimes. For example, for a PDF containing a price list, the author might update the existing Silverstripe asset instead of creating a new one, so that it is available on the same URL. That's why I set caching of these to one day or 86,400 seconds. This way, when the price list PDF gets updated, it will take a maximum of one day for every user to get the new version.

CSS, JS  and font files, I set the max-age to one year, but I also set it to immutable. With that, I tell the browser that these files will never change. (Browsers will cap the max-age value to 1 year anyway, so there is no point in setting a higher value.) When setting immutable, I can also remove the ETag and Last-Modified revalidation headers, because if something can be cached forever, these revalidation headers are obsolete. The browser never needs to check if the file has changed.

Build your website responsibly

With responsive web design, there is a lot that can be done to improve the page load time of your website. Not every device should receive your fully-fledged site with all the huge images and nice-to-have functionality. Sometimes (a lot of times) less is more.

I highly recommend Scott Jehl's book  Responsible Responsive Design from A Book Apart. It takes you through all the things that need to be considered when delivering truly responsive websites, taking into account not only screen size, but also different network speeds and device capabilities.

Use a CDN for global reach

There are situations where you've done everything reasonably possible or where a site is performing well in Australia, but not in the US. I have a client who operates in Australia, with their customer base being in Australia, the US and Europe. 

A CDN is a network of geographically distributed caching servers. They are used to cache static content, for example, images, videos and CSS and JavaScript files. 
Cloudflare is probably the best-known CDN these days, but I prefer Fastly for its flexibility. Both of them use Varnish cache as their caching backend, but Fastly gives you full control over the whole Varnish configuration, whereas Cloudflare only allows very little customisation.

A CDN reduces the network latency by caching the content on a server closer to the user. If, say, a user is located in Austria, and your server is in New Zealand, the user will have to wait for the content to be sent from New Zealand to Austria. This network latency is around 300ms between Europe and New Zealand, and around 100ms between Europe and the USA. Network latency impacts every request: for the page, every image, every CSS and every Javascript, and can
have a significant impact on the perceived performance of your website.

CDN caching, or caching on the edge as it's called as well, reduces the load on your server, which can help a lot on high traffic sites. CDNs are also used to improve the availability of your website. If your server goes down temporarily, the content can still be served from the CDN.

Caching pages on the CDN

The CDN will store a copy of your static files by using the browser caching we added to the .htaccess file earlier. But you can also cache whole pages on the CDN.

For that, you need to add the caching headers to your page requests. In Silverstripe, you can do that in the init() method of the PageController, before calling the parent init() method:

class PageController extends ContentController
{
    protected function init()
    {
        HTTPCacheControlMiddleware::singleton()
            ->publicCache()
            ->setMaxAge(600) // 10 minutes
            ->setSharedMaxAge(3600); // 1 hour         parent::init();         ...
    }
}

You load the HTTPCacheControlMiddleware and set the cache to public. To make the page cacheable for, say 10 minutes, you set the MaxAge to 600 seconds.

Silverstripe forms disable the public page cache automatically. This is to protect user data from being exposed to other users. If you have a global form on the page, for example, for search, you need to either make it cacheable by setting the form method to GET and disabling the security token, or load the form via AJAX.
Generally, if you have personalised content on the page and still want to cache the whole page, you need to load the personalised content via AJAX.
Also, when someone logs in, you have to make sure the page isn't cached anymore. And when they log out, you have to clear the cookies to make sure they get served the cached content again.

In addition to MaxAge for browser caching, you can also set SharedMaxAge, which determines how long the response can be cached by shared caches like CDNs. If SharedMaxAge is not set, the CDN will fall back to MaxAge.

To make the integration of a CDN into Silverstripe easier and automatically clear the CDN cache when pages and other objects are published or deleted, you can use one of the following modules:

These modules automate managing the CDN cache for website authors.

 

 

updated 25/1/2017: after re-publish on Silverstripe.org
updated 5/8/2025: fix 404 links
updated 17/02/2026: fix 404 links
updated 06/10/2026: update information after my talk at Stripecon EU 2026