Performance optimization isn’t easy. There are a lot of possibilities out there and each one needs to be tested before deciding which is best. For example, while working on one of the two sites I discuss in this article, I tried out Redis as an object cache. Typically this is a good option, but it slowed down some of my pages, caused problems in others, and exhausted the memory.
In this article, I want to discuss WordPress performance optimization, mainly in terms of CDN based on some recent experience of mine. This isn’t a definitive guide to optimization or a walk through of how to setting up some plugins.
Not every solution works in every place, but it’s important to examine everything you can. Let’s begin with the pros of using a CDN.
Why A CDN?
It’s well-known that CDNs are important because they increase the performance of your site by serving the assets from a server close to the end user. But there are two other potential benefits that you can get from a CDN.
The first is that browsers will only make a set amount (6-11 depending on the browser) concurrent HTTP/S requests to the same browser. This caused an interesting problem on the Caldera Forms site. I had enqueued the child theme CSS with a very high priority because it was made up of overrides from the parent theme.
Since I had several other JavaScript and CSS files loading that were being served from the site, the child theme.css took a little longer to load. As a result on some pages, the layout started wrong and then adjusted. I will explain in this article my approach to using a CDN to reduce the number of requests coming from the same domain.
The other potential benefit is that moving assets out of the main server will reduce the load. This was a major benefit to a site optimization project I worked on recently. Normally this isn’t a big gain, but I was looking for cheap wins before starting on the other issues causing 6-8 second load times on that site. Moving images to a CDN reduced server load and took a 1 to 1.5 seconds off of page load time with very little work.
Start Free
CDNs are great, but they are not cheap. You will be paying for bandwidth and storage. Free CDNs do support any of the JavaScript and some of the CSS files you need for a WordPress site. Using a free CDN for a common library can save you money and increase the chances that the browser will already have the needed files cached.
On the Caldera Forms site redesign, we used Bootstrap in our theme. I used jsdelivr to serve Bootstrap’s CSS and JavaScript. For those files, I just used wp_enqueue_script/style with the CDN url.
For libraries loaded from core, I used a mix of CDNs. My main concern was making sure that I didn’t load the wrong version of the library. Here is what I put in my mu-plugins directory to replace core jQuery with a CDN version:
add_action( 'init', function(){ if ( ! is_admin()) { if( is_ssl() ){ $protocol = 'https'; }else { $protocol = 'http'; } /** @var WP_Scripts $wp_scripts */ global $wp_scripts; /** @var _WP_Dependency $core */ $core = $wp_scripts->registered[ 'jquery-core' ]; $core_version = $core->ver; $core->src = "$protocol://ajax.googleapis.com/ajax/libs/jquery/$core_version/jquery.min.js"; if ( WP_DEBUG ) { /** @var _WP_Dependency $migrate */ $migrate = $wp_scripts->registered[ 'jquery-migrate' ]; $migrate_version = $migrate->ver; $migrate->src = "$protocol://cdn.jsdelivr.net/jquery.migrate/$migrate_version/jquery-migrate.min.js"; }else{ /** @var _WP_Dependency $jquery */ $jquery = $wp_scripts->registered[ 'jquery' ]; $jquery->deps = [ 'jquery-core' ]; } } },11 );
This gives me jQuery from Google’s CDN, but instead of just deregistering core JavaScript, I need to do two important things. First, I don’t deregister it, I just change its URL. Secondly, I make sure to use the version number for jQuery loaded by core. This future proofs my script.
One risky thing I did was remove jQuery migrate. That library adds functionality removed from jQuery back in. Be careful removing this. Some plugins or themes might break.
Spreading CSS and JavaScript Across Multiple CDNs
On the Caldera Forms site, I started using KeyCDN to load JavaScript and CSS using their CDN enabler plugin. This is a good plugin, but it only supports one zone.
As I said earlier, one of my goals was to make sure my child theme’s CSS loading would not be delayed. So I wanted to make sure that it was coming from a domain with less than 6 resources. This means required multiple zones on KeyCDN, which made me stop using their plugin.
Since my theme and child theme load most of their resources from public CDNs, or the Cloudfront CDN I built for shared CSS and images used across different Caldera sites, the theme directory only has 5 files total between CSS and JavaScript, and not all of them are used on every page load.
So, I set up two zones on KeyCDN. From my KeyCDN dashboard, I set up two zones. For one I used https://calderaforms.com/content/plugins as the origin and for the other, I used https://calderaforms.com/content/themes as the origin.
KeyCDN will automatically pull in all of your files. It’s also reasonably priced and super easy to setup. Compared to rolling your own CDN with AWS s3 and Cloudfront, it’s really super dreamy.
The only downside is the defaults are non-sensible. By default, they disable HTTPS support and automatic redirect to HTTPS. This seems like a mixed content warning asking to happen.
Once I had the two CDNs deployed, I used the script_loader_src and style_loader_src filters to change the URLs of files in my plugins and theme directories to use the CDN URLs when they were loading.
First, let’s look at the callback that I’m using for both filters:
<?php /** * Change URL of CSS/JS for CDN * * @param string $url * * @return string */ function caldera_moo_cdn_filter( $url ){ $theme_dir = untrailingslashit( WP_CONTENT_URL ) . '/themes'; if ( 0 === strpos( $url, $theme_dir ) ) { return str_replace( $theme_dir, 'https://cfdotcomthemes-6081.kxcdn.com', $url ); } $plugin_dir = untrailingslashit( WP_CONTENT_URL ) . '/plugins'; if ( 0 === strpos( $url, $plugin_dir ) ) { return str_replace( $plugin_dir, 'https://cfdotcomplugins-6081.kxcdn.com', $url ); } return $url; }
The important thing here is that I’m doing a check using strpos() to identify if the URL is in the theme or plugin directory. This is important because I am using two different CDN URLs. Also, there are plenty of other URLs that could be affected by this filter that I don’t want to change. Assets coming from core, or other CDNs must not be affected.
I hooked that callback to both filters, inside of a closure to make sure that we were not in the admin. Also since I’m using environment variables for my site’s configuration, I was easily able to skip using the CDN when not in a production environment.
<?php /** CDN */ if ( ! is_admin() && isset( $_ENV['WORDPRESS_ENV'] ) && in_array( $_ENV['WORDPRESS_ENV'], [ 'production' ] ) ) { //JavaScript add_filter( 'script_loader_src', 'caldera_moo_cdn_filter' ); //CSS add_filter( 'style_loader_src', 'caldera_moo_cdn_filter' ); }
The AWS Solution
Above I said solutions for a site I built to be efficient and my only real issues were optimizing asset loading. But, as I mentioned in the intro, I recently took over a WooCommerce site with major performance issues. It was a mix of issues that come from a massive database, WP CRON issues, a poorly coded theme from ThemeForest and issues that could be solved with CDN. I’ll the other issues you can encounter in a future article.
But on that site, I moved all the images, CSS and JavaScript storage to AWS s3 and serving them via Cloudfront. This gave me immediate performance gains in terms of what you expect a CDN to do and reducing server load.
AWS is a really powerful and cost-effective solution that I am very impressed by. I’m now using it for hosting a lot of my WordPress sites. That said, user interface and ease of use are not AWS’s strong suit.
Luckily the good people at Delicious Brains — of WP Migrate DB Pro fame — make an excellent plugin WP Offload S3. This plugin is very well documented. I’m not going to walk you through its settings, someone else can write that tutorial.
The core plugin handled moving images from the media library to s3 and the assets add-on handled serving CSS and JavaScript via Cloudfront. I will say that while the plugin’s user interface is exceptionally well done, you still have to do a lot of setup in AWS and that might drive you crazy.
This plugin was definitely worth the $200 because it automates the process of keeping files in sync, cache busting for updates and most of the configuration you would want to do once things are setup.
There Are No Magic Bullets
It’s important to keep in mind that CDNs are not magic bullets. Sometimes your server can’t handle the traffic. Other times your plugins or themes are just bad. In the case of the client site, the biggest gain we got was from ditching their theme and creating a custom theme. This wasn’t a cheap process, but the site looks better and loads faster.
One thing I’ve learned about the value of custom themes is the value of removing options and modularity. Often times commercial themes get a bad rap and are labeled as being bloated and slow. Most of the time that is because they have to provide so many solutions. Each possible setting needs to be saved somewhere and then the code that implements those options needs a bunch of conditionals. There is only so much you can do to optimize those types of themes.
I hope this article has helped you think about CDNs more critically in terms of exactly what problems they are solving. WordPress performance optimization discussions too often end at the dogma of “CDN good” and “cache good” and don’t think critically of the pros/cons of each or encourage testing of the solutions.
No Comments