Julian is one of our SilverStripe developers who works with the rest of the team on large projects, as well as acting as technical lead on projects of his own. He is an American citizen who has never lived in America. He was born and grew up in Germany, studied in the UK, and is now happily living in New Zealand, whose friendly, laid-back culture and natural beauty he values.
We all want our websites to be fast. Customers prefer faster websites, they buy more, stay longer, and experience more enjoyment, all because of a faster site. Moreover, research indicates that people are getting increasingly less patient with slow websites:
- How Long Will You Wait For a Web Page to Load?
- For Impatient Web Users, an Eye Blink Is Just Too Long to Wait
Additionally, faster page downloads from a website means reduced server load per page view. Reduced load means the server is able to handle more concurrent users. More concurrent users means you can run your site on cheaper hardware and use a simpler deployment. Perhaps a single server will do where you would otherwise have to buy, deploy and manage four servers running in parallel. Improving performance is a win, win, win.
So, as web developers, we are naturally thinking of ways in which we can speed up our websites. Here is a guide for things you can do with SilverStripe to improve performance.
Measuring
The first step should always be to measure how fast your site already is. That will allow you to see how much your changes improve things. Here are some tools that you can use to measure your website’s speed:
Okay, now that we’ve measured our current speed. Here some practical tips for making things faster.
Static caching
Static caching makes a huge difference in page speed. Instead of SilverStripe generating each page dynamically on every request, it can cleverly write a “static” file with the contents of a page to the filesystem. A web server like Apache, Nginx, or LightTPD can serve up static files much faster than PHP can build a dynamic page (like one or two orders of magnitude faster). The difficult thing is knowing when to update the static pages and/or how to manage the parts of the page that need to be dynamic.
We use static caching to great effect on the websites we build that need to hold up to a huge amount of traffic. Read this guide on how to set up SilverStripe with static caching.
Partial caching
Partial caching is not nearly as intimidating to set up as static caching. However, it provides some very nice performance gains for relatively little effort. The idea behind partial caching is that parts of your website, for example, a menu that goes 5-levels deep rarely change and therefore do not need to be re-calculated on every single page request. With partial caching we set a cache-key (e.g. the maximum last edited value of the SiteTree database table) that is quick to query. As long as the cache-key remains the same, i.e. as long as no one updates the SiteTree structure, we fetch that part of the page HTML from the cache storage instead of generating it with PHP code.
If done correctly, partial caching can give you a 2x - 5x performance boost with no downsides. Choosing the correct cache-key is critical though. With the right cache-key, users will never even know that caching is there working its magic.
Every website should implement some partial caching. Read more about it in this comprehensive guide.
Combining and minifying Javascript
We’ve already covered this in our Javascript performance tips. See tip number nine here.
Combining and minifying CSS
If you use a CSS-extension like SCSS you can use that to combine and minify your CSS. SCSS is great for a whole bunch of other reasons too. It takes much of the pain out of writing CSS. Try it!
To combine your CSS, first write it in nicely separated SCSS files (SCSS is fully backwards compatible to CSS, so you can just copy and paste if you are in a hurry), then import all these files into a single “style.scss” file that looks a bit like this:
@import "_clear.scss";
@import "_typography.scss";
@import "_layout.scss";
@import "_shared.scss";
Compile your SCSS into CSS, then using the SilverStripe Requirements system, include the single CSS file that was generated. You might put something like the following in your “Page_Controller” class:
public function init() {
parent::init();
Requirements::themedCSS('style');
}
In addition to combining CSS, you can use a tool like Compass to help you minify the CSS code. If you want maximum performance, then set the following in your “config.rb” file (when you create a new Compass project the Compass compiler will create this configuration file in your project directory):
output_style = :compressed
That configuration setting tells Compass to minify the output CSS, removing all unnecessary comments, white-space and newlines. It can be a bit disconcerting to see all your site’s CSS on a single line, but it nicely reduces the amount of data the browser needs to download and parse.
The end result of all this is that you get a single minified CSS file for your entire website where you would otherwise have a bunch of different files, all of which the browser would have to download separately, slowing down your website.
Htaccess tweaks
The htaccess file controls what Apache does when serving content. However, tweaking these settings is a bit of a black art. Similar to Quantum Mechanics, I’m pretty sure noone knows exactly how htaccess directives work (haha).
SilverStripe deliberately keeps its basic htaccess file quite simple. Hopefully some people can therefore fully understand what is happening in there and modify the htaccess file to suit their needs. Here are some tips on what you can add to your htaccess file to gain immediate and significant performance benefits for your website.
Expiry headers
We can set expiry headers in the .htaccess file. This will tell the user's browsers that it should store/cache certain static files for a length of time and not re-request them on every page view. So the browser downloads all the static resources when the user first visits the website, but then never again for the next seven days, using the cached copies instead.
It does this with one exception: SilverStripe automatically adds a clever “cachebuster” suffix (e.g. “.../screen.css?m=1312462080”) to the end of Javascript and CSS files included through the Requirements system. So, when you release a new Javascript file, the changes will take effect immediately because the new suffix tricks the browser into seeing a new file, bypassing the cached version (note: SilverStripe creates the suffix using the “filemtime” function based on the modification date of the file).
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/html "access plus 5 minutes"
ExpiresByType image/gif "access plus 7 day"
ExpiresByType image/png "access plus 7 day"
ExpiresByType image/jpg "access plus 7 day"
ExpiresByType image/jpeg "access plus 7 day"
ExpiresByType image/ico "access plus 7 day"
ExpiresByType text/css "access plus 7 day"
ExpiresByType text/javascript "access plus 7 day"
ExpiresByType application/x-javascript "access plus 7 day"
</IfModule>
Notice how this example includes a 5-minute cache of the HTML itself. Any change to the site will take five minutes (or a hard refresh) to appear, but you get some nice additional performance in times of high load. If your content doesn’t have to be up-to-the-second fresh, then adding an HTML expiry header might be a good idea.
Disabling e-tags
If you are sure you got your expiry settings right, then you can override the browser’s checking for expired content by disabling “if-modified-since” and “e-tags”. This means that the browser doesn’t send a quick “is this really expired” message to the server, but instead fully relies on the values set in the original file header. The result: even better performance, especially so in a multi-server environment.
<IfModule mod_headers.c>
Header unset ETag
RequestHeader unset If-Modified-Since
RequestHeader unset If-None-Match
FileETag None
</IfModule>
Compressing content
Just about every web-browser (yes, even IE6) supports compressing of the text-content on a webpage. Compressing text content can drastically reduce the amount of data the server needs to transfer to the client. You can save as much as 80% of the bandwidth by using compression.
However, beware, attempting to compress files that are already compressed can sometimes cause problems, even potentially corrupting the files in question. So be careful to exclude such files from the compression.
Here is how to add “deflate” compression.
<IfModule mod_deflate.c>
SetOutputFilter DEFLATE
BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4\.0[678] no-gzip
BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
# Don't compress images, movies or zip files
SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip dont-vary
SetEnvIfNoCase Request_URI \.(?:exe|t?gz|zip|bz2|sit|rar)$ no-gzip dont-vary
SetEnvIfNoCase Request_URI \.(?:avi|mov|mp3|mp4|rm|flv|swf|mp?g)$ no-gzip dont-vary
<IfModule mod_headers.c>
# properly handle requests coming from behind proxies
Header append Vary User-Agent
</IfModule>
</IfModule>
Note: if “deflate” compression gives you trouble, then you can try “gzip” compression in Apache as an alternative.
Do you know of any more we can do to improve site performance? Please share your own tips and insights in the comments.