Categories

WordPress and Speed

Did you ever speed test your WordPress site and not like the results you got? You may have come to a quick conclusion that WordPress must be slow, or my VPS is slow, in my opinion that may not be the case, I was able to get this fresh WordPress site up to 100% across the board scores with two free plugins a few lines of code in the .htaccess and a nginx reverse proxy.

The first time I saw a web page was back in 1994 it was on a floppy disk, one HTML file and a few GIFs. After discovering that you could view the source, alter it and FTP it to your own web server, I had discovered web publishing. This gave me a great sense of empowerment, here I could make websites that could rival corporations and governments, and sometimes even be better than them. It probably looked something like this.

Mosaic Browser 1994

It was grey, it was basic but it worked, and it was slow! not because of the content but because the average modem speeds were 14kbps. Over the years the average speeds got better, today I have 250mbps, but the content size also grew over those years where today we can see websites that average around 3 Megabytes. An average website may load well on a fast broadband connection but on a 3G connection that is not beside a cell tower it could take up to 15 seconds for a page to load.

One way to get around that problem is to serve a separate mobile site by detecting the user-agent, and serving a heavier desktop site where you would expect better connections. But this creates extra work load for the publisher maintaining two outputs of the same content, Content Management Systems can help here by having two themes and the content siloed in a database. But the current trends is to use a CMS but serve one site that fits all by being responsive or adaptive or both.

We only accept Cache

This brings in a database beside HTML/CSS/Javascript/Imgaes/Video and the database can be the first point of weakness for a CMS. By default every request to the website makes a connection to the database to gather the content, in my experience this is where things start to slow down. To fix this issue a Cache is needed to collect the content from the database and store it and serve it statically, like we did back in 1994. Using WP Super Cache plugin for WordPress achieves this and the results are immediate, the Time To First Byte can drop from around 4000ms to around 500ms.

This will help improve the speed index of a site, Google says that if the speed index goes over 3000ms 53% of mobile users abandon their visit to a site, and that means they never get to see your website and may never come back.

The next plugin I used was Yoast SEO, this allowed for a description to be added for the page and with a default theme like Twenty Twenty, the SEO score on web.dev went up to 100%.

Securi Core Cares

The performance and best practice scores on web.dev were still around 60 – 70% so after a read of the details, it was apparent that there were a few security based issues, so I moved over to configuring the Web Server (Apache) which is simply done by editing the .htaccess file in the root of the web server. A basic .htaccess for a wordpress site would look like so:

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>

# END WordPress

So I added the following below to it:

## BEGIN GZIP Compression ##
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
AddOutputFilterByType DEFLATE application/x-httpd-php
AddOutputFilterByType DEFLATE application/x-httpd-fastphp
AddOutputFilterByType DEFLATE image/svg+xml
SetOutputFilter DEFLATE
</IfModule>
## END GZIP Compression ##

## BEGIN Vary: Accept-Encoding Header ##
<IfModule mod_headers.c>
<FilesMatch "\.(js|css|xml|gz)$">
Header append Vary: Accept-Encoding
</FilesMatch>
</IfModule>
## END Vary: Accept-Encoding Header ##

## BEGIN Leverage Browser Caching (Expires Caching) ##
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/css "access 1 month"
ExpiresByType text/html "access 1 month"
ExpiresByType image/jpg "access 1 year"
ExpiresByType image/jpeg "access 1 year"
ExpiresByType image/gif "access 1 year"
ExpiresByType image/png "access 1 year"
ExpiresByType image/x-icon "access 1 year"
ExpiresByType application/pdf "access 1 month"
ExpiresByType application/javascript "access 1 month"
ExpiresByType text/x-javascript "access 1 month"
ExpiresByType application/x-shockwave-flash "access 1 month"
ExpiresDefault "access 1 month"
</IfModule>
## END Leverage Browser Caching (Expires Caching) ##

## BEGIN Disable ETag header ##
Header unset Pragma
Header unset ETag
FileETag None
## END Disable ETag header ##

Header always set Strict-Transport-Security "max-age=31536000;
Header set Access-Control-Allow-Origin "domain"
Header set X-Content-Type-Options nosniff
Header set X-XSS-Protection "1; mode=block"
Header set Content-Security-Policy "block-all-mixed-content; upgrade-insecure-requests;
<FilesMatch "\.(ttf|ttc|otf|eot|woff|font.css)$">
    Header set Access-Control-Allow-Origin "*"
</FilesMatch>
Options -Indexes
SetOutputFilter DEFLATE

The site also needed to be SSL enabled, so a free SSL cert from letsencrypt.org was installed with non SSL http traffic being redirected to https (note this redirect method may not work for all types of content eg: shoutcast streaming).

And with that, the site was getting results of 96% – 100% – 100% – 100%, so what was up with the performance only getting 96%?, a little bit of reading into the details of the issues led to the idea of using an NGINX reverse proxy in front of the web server that could serve the site with HTTP2 and do some fine tuning on the SSL side. I’ll leave the details of this for another post, but the end result was 100% score across the web.dev disciplines.

Theme Parked

The theme for this project was the default Twenty Twenty that ships with a fresh install of WordPress, multiple experiments with themes from the vast ~8000 free themes available from WordPress gave very poor results across the board. My advice is to steer clear of them and develop a child theme yourself, this means getting up to speed with a bit of CSS and PHP but at least any future degradation of speed test results will be fixable in your code rather than sifting through someone else’s code. A word of warning on editing default themes, although it may work, next time that theme gets an update your changes will be wiped. So always use a child theme to protect your changes.

Other things to look out for is adding plugins, these can affect your performance score, so always test your sites after installing new plugins so you can at least know which ones are affecting performance, and prior to testing always purge the cache so that the static cache is updated with the changes. This also brings in the need to test twice where the first test is capable of rebuilding the cache and the second test would give you a more accurate result.

The main principles of website performance were drawn up by Steve Souders, rule one is very important, sometimes you can see sites with 200+ requests that make up one page, the main offenders here would be social widgets that can bloat the request load by around 15 request per widget, so a few like buttons and social embeds or even just one youtube video can cause nearly 50 requests that you would have little control over.

Rule 1 - Make Fewer HTTP Requests
Rule 2 - Use a Content Delivery Network
Rule 3 - Add an Expires Header
Rule 4 - Gzip Components
Rule 5 - Put Stylesheets at the Top
Rule 6 - Put Scripts at the Bottom
Rule 7 - Avoid CSS Expressions
Rule 8 - Make JavaScript and CSS External
Rule 9 - Reduce DNS Lookups
Rule 10 - Minify JavaScript
Rule 11 - Avoid Redirects
Rule 12 - Remove Duplicate Scripts
Rule 13 - Configure ETags
Rule 14 - Make AJAX Cacheable

If your site has a high load of small icon sized images, consider turning these into a sprite which would reduce the request load down to one image. If your CSS gets very big consider minification, the same goes for Javascript.

Redirects also cause a lot of latency, redirects are a handy way to update old content but they should only be used as a fix and not designed into a way of delivering content. There are WordPress Plugins that can help DNS pre fetching if you have content that is coming from many sources, but also consider taking these local if they are static or pulling them over on a CRON job every hour if they are dynamic.

Too many CSS or Javascript requests can also be reduced by merging them in to one file, and there are a few Plugins that can assist in this but they all have different degrees of success.

Public_Images Limited

A picture tells a thousand words, but it can also take many thousand more bytes even millions to tell those stories. But also this isn’t 1994 and the web is a very graphical environment, so choose imagery selectively, choose colour-full backgrounds and gradients that can reduce the need for images. Or use background colour and gradients in conjunction with images giving the impression that the image is larger than it actually is.

I recently saw a 13Mb animated GIF89a on a site where surely a compressed video would have had better compression, so compress images to suit the display size of your target displays. Often CMS users upload 20Mb images taken on their phone, upload them to the media library and then display them at a fraction of their original size. There are Plugins available (usually commercial) but they do a great job at compressing images and some throw in a CDN service that serve your images from edge servers around the world meaning your international users get to download the images from servers that are much closer to them.

If you’re not in the mood for paying for a image compression service it is surprisingly easy to spin one up yourself. Install imagemagick on your server.

Take the following code and run it in a Direcory that hosts your jpgs.

for file in *.jpg; do convert $file -quality 50 -define webp:lossless=false,method=6,auto-filter=true,partitions=3 "`basename $file .jpg`.webp"; done

Or for PNGs

for file in *.png; do convert $file -quality 50 -define webp:lossless=false,method=6,auto-filter=true,partitions=3 "`basename $file .png`.webp"; done

That will create a copy of the original jpg or png with an extension webp, a new image compression format, then in your .htaccess in the root of the web site place the following code.

<IfModule mod_rewrite.c>
        RewriteEngine On
        RewriteCond %{HTTP_ACCEPT} image/webp
        RewriteCond %{DOCUMENT_ROOT}/$1.webp -f
        RewriteRule (.+)\.(jpe?g|png)$ $1.webp [T=image/webp,E=accept:1]
</IfModule>

<IfModule mod_headers.c>
        Header append Vary Accept env=REDIRECT_accept
</IfModule>

AddType image/webp .webp
#if your site is in a sub folder, then this might need adjusting

This will only serve the webp image versions to browsers that support webp and serve the original png or jpgs to older browsers.

Content Delivery Networks

CDNs are number two on Steve Souders list, they offer a way to allow the end users get their content from severs that a geographically placed nearer to them. Usually they handle the heavier loads of video and images, but they can also serve any static assets like CSS, Javascript and Fonts. It is always useful to have full control over your content on a CDN where you can purge the cache if the CDN is serving outdated content.

There are free CDNs available, notably Site Accelerator from Jetpack, but with the inability to purge the cache or even delete it permanently you may choose to go with a paid service. Not all CDNs offer the same services, some have servers in the cloud others have them embedded in the Telcos where they are only the last mile away from the end users. Some offer add-on services of web security and content optimization.

Video killed the requests budget star

To make this page a bit more of a real world example a youtube video has been added, this video is lazy loaded by the Lazy Load Videos plugin. When the video comes close to being visible in the viewport its poster is requested and displayed.

https://youtu.be/4XHEPoMNP0I

On clicking the play button a further 40 requests are made to YouTube and the video plays, these 40 additional requests are shielded from page load until they are needed.