Adding and Leveraging a CDN on Your Website

If you’ve been working around web development for a while, you’ll know that the web has a need for speed. The truth is simple: speed matters and faster is considered better.

One "easy win" for increasing performance is using a CDN. We’ve discussed this several times at CSS-Tricks. It's easy in that you aren't rewriting a codebase or hand-editing content, but it's an area of performance many of us might overlook because, well, hosting, servers and cPanels, oh my!

Thankfully, the process for integrating a CDN does not have to be hard. Ideally, you get things configured and then things like Google PageSpeed shriek with glee in the form of high performance scores which, in turn, yield to better search engine rankings, higher revenue, a raise for you and...you get the point.

This post is going to walk through the process for setting up a CDN to work on a custom web application, followed by some tricks we can use to get the most out of our work. Like I said, it doesn't have to be hard, so let's dive in and make the web faster!

It's worth noting that setting up a CDN can be even simpler than what we're covering here for those working with a CMS like WordPress, where plugins can help with the lift. We're going to assume that not everyone has that luxury (shout out to web applications!) and proceed in the more traditional way.

Hold up, you lost me at CDN

It doesn't hurt for us to review the basic concepts of what a CDN is and what they're used for, so let's start with that.

A CDN works by fetching static assets from your site's server and caching them on their servers. When a browser makes a request to visit your site, the static contents are served via the CDN rather than by your site. This is faster because the CDN servers are globally distributed and serve as proxies that determine which server is located physically closest to your visitor, thus delivering content on a speedier, more convenient network. Hence the full name: Content Delivery Network.

A Content Delivery Network is globally distributed for optimized delivery of static assets

So, how does the browser know to get the resources from the CDN instead of your server? The URL of your static resources are replaced to point to the CDN server instead of the URL of your own site.

For example, say we want CDN to be configured as a subdomain. For CSS-Tricks, that might be something like cdn.css-tricks.com and that will be the relative URL for which we base all our assets.

Put more succinctly put, the URLs of assets on our site like this:

http://www.css-tricks.com/image.jpg 
http://www.css-tricks.com/style.css 
http://www.css-tricks.com/script.js

...would become:

http://cdn.css-tricks.com/image.jpg 
http://cdn.css-tricks.com/style.css 
http://cdn.css-tricks.com/script.js

The browser sends the requests to your CDN rather than your server, taking the load off your server and making your whole site faster as a result.

Wait, two servers for one site?

Yes, to a degree, but it doesn't really mean that you're managing two servers.

What we're talking about instead is using the CDN as a virtual layer that stands between your server and a user's browser. That virtual layer listens in on the request the browser is making to your server and gladly responds with cached assets on behalf of the server.

In some cases, you may upload your asset directly to the CDN and take the entire burden off your server. What we'll be looking at in this post instead is a process where the CDN fetches assets from your server and pre-caches them so there's no need to upload to different servers and manage multiple locations.

How to implement a CDN on a Custom Application

Two of the most widely used CDN services are Amazon AWS and MaxCDN, though plenty of other services are certainly available. We'll be focusing most of our attention on MaxCDN as an example of how to set things up.

Step 1: Choose a CDN and Register Your Site

Once you’ve decided that a CDN is the way to go for your web application, you’ll want to register for an account. There are many, many options out there and, rather than weigh the pros and cons of each (which might make a good future post), here are a few to get you started:

A common step when registering your account is to set up a pull zone or distribution. A pull zone or distribution is used by the CDN as a bucket for your everyday support files. It will automatically pull the data from a location that you specify upon the first request for the file. The files are served to the end user from either a subdomain of your CDN of choice or a custom domain (sub domain) of your choice that points to the CDN. The data in the bucket is automatically purged from the server after a customizable amount of time.

Step 2: Create your CDN URL

Your CDN URL is the URL all of your assets will point to once things have been set up. A good rule of thumb is to use a URL name that is easy to do a search and replace in your database for all of your existing URLs.

Like any other subdomain, this will need to be set up as a CNAME record in your host's DNS settings.

A typical cPanel screen for configuring CNAME records with a host

Step 3: Point Your Assets to the CDN

Let's look at a method for creating a variable for the CDN URL and how it can be used to help programmatically prepend the URL to our static resources. The reason we want to do this is that (1) it makes it tougher to make mistakes in our markup and (2) it is easier to maintain the URL should we need to change it.

We’ll do this is by defining a global CDN variable, then prepend this variable to our static resources URL. We define this variable at both the PHP level and the JavaScript level so that we have more flexibility in how we use it down the road. It also makes it easier for us if we decide to ditch the CDN because all we have to do is replace the variable with a simple / to get things back to the relative path of our original server.

Note that the following examples are here to serve illustrate examples rather than to be used literally. Your actual usage may vary.

<?php
define('cdnURL', 'http://cdn.css-tricks.com/');
?>
<html>
    <head>
        <title>Hello World!</>
        <script type='text/javascript'>
            /* Let’s define a javascript global for using the CDN inside scripts */
            var cdnURL = '<?php echo cdnURL ?>';
        </script>
        
        <link rel='stylesheet' href='<?php echo cdnURL ?>css/style.css' />	
    </head>
    <body>
        <img src='<?php echo cdnURL ?>img/logo.png' />
        <button>Submit</button>
        <script type='text/javascript' src='<?php echo cdnURL ?>js/main.js'></script>
    </body>
</html>

Or, done in JavaScript:

(function() {
    var preloadImage = document.createElement('img');
    preloadImage.src = cdnURL + 'img/logo.png';
})();

This does require a slight change in your thought processes as a developer. Every static resource needs to get the cdnURL variable prepended to it.

Same thinking goes for your CSS. For example, we can also setup a global CDN variable and prepend it to our CSS resources using a CSS preprocessor, like LESS:

@cdnURL: 'http://cdn.css-tricks.com/';
button {
  background-image: url('@{cdnURL}img/button.png');
  &:hover {
    background-image: url('@{cdnURL}img/button_hover.png');
  }
}

...or Sass for that matter:

$cdnURL: 'http://cdn.css-tricks.com/';

button {
  background-image: url('#{$cdnURL}img/button.png');
  &:hover {
    background-image: url('#{$cdnURL}img/button_hover.png');
  }
}

The great thing about this is that you can switch off your CDN by simply setting the cdnURL to / which will recreate all of your relative URLs. This also has the advantage that should you want to switch the CDN URL, you just need to change the cdnURL.

That is really the three-step process for setting up a CDN, linking it to your server and then pointing your existing assets to it for so the CDN can serve things up when requested.

Let's talk about some advanced settings

Setting up the CDN wasn't that scary, right? Now that we've gotten past the main hurdles, we can have some fun with advanced settings that optimize the way our assets are served.

TimeToLive (TTL)

CDNs typically have a TimeToLive (TTL) set for assets. This is a fancy way of telling the CDN how long (in seconds) before it should treat an asset as stale. At that point, it looks it up on the server again for a "fresh" copy.

The longer the TTL, the longer a "version" of that asset will stay with the CDN and continue to be served. The shorter the TTL, the more often it ditches the "version" of the asset it stores and goes back to the original server to look for an updated version.

MaxCDN cache settings screen for setting TTL expirations

Invalidating assets

The double-edged sword with TTL is that you can update an asset on your server and the change will not reflect on your site until the TTL has expired and the CDN makes its next stop to the server to find a new copy.

We can overcome this by invalidating an asset. The trick here is to change the filename on update. If the filename changes, the CDN doesn't know any better and reads the newly named file as a completely new asset rather than an update to the existing one.

In other words, this: http://cdn.css-tricks.com/image100.jpg

...would be renamed to something like this: http://cdn.css-tricks.com/image101.jpg

So long old version and hello new one!

Leveraging TTL for version control

Hey, so if the CDN is holding onto one version of an asset and there is a fresh copy of it living on our server that it hasn't fetched yet, then we technically have two iterations of the same asset. We can use this to create a form of "version control" where updating our assets on the server doesn't mean we automatically lose them and can revert to a past copy, if needed.

The really complicated way to do this is to rename all of your resources every time you make a change like we did when invalidating the asset. However, this is overkill from a maintenance perspective, even if we had to create a variable to do this like we did for the cdnURL. We’re going to do some cheating instead because that's how we roll on a blog that is built around tricks.

We'll start by placing our static assets into their own folders so that this: http://cdn.css-tricks.com/image.jpg

...becomes this: http://cdn.css-tricks.com/img100/image.jpg

To invalidate the file and force the CDN to serve the latest version, we would modify the subdirectory path like this: http://cdn.css-tricks.com/img101/image.jpg

See the difference? The filename stays the same but it now appears to live in a directory on the server. Again, the CDN does not know the difference and sees this as a completely new file. We just created a faux form of version control that happens directly in the folder!

But wait, we can do better.

Changing the number on a folder on every update is still a cumbersome process and yet another step in the maintenance of our site. What we can do instead is make a few small changes to the site's .htaccess file to do the heavy lifting for us.

We'll outsmart the CDN by serving all our assets from the same folder, but make it look like it’s being served from a versioned folder, thanks to some rewrite rules.

<IfModule mod_rewrite.c>
	RewriteEngine On
	RewriteBase /
	RewriteRule ^ver-[0-9]+.[0-9]+.[0-9]+(.*)$ $1 [L,NC]
	RewriteCond %{REQUEST_FILENAME} !-d
	RewriteCond %{REQUEST_FILENAME} !-f
	RewriteRule ^(.*)$ index.php?/$1 [QSA,L]
</IfModule>

Ha! Now our server is spoofing the version number in the URL while delivering our example image.jpg asset from the same path on the server where it was originally uploaded.

Invalidating all assets on demand

The last thing to round this out is to integrate our version control trick into our HTML markup so that the CDN will refresh all of the assets as soon as we want to fully invalidate them.

We'll set up a config file where we define a variable for the version number, import that variable into our asset URL structure and then change the version number each time we want to push a refresh of our assets across the board.

Here's what that might look like in a PHP config file:

VERSION
1.0.0

Configure::load('cdn');
define('VERSION', file_get_contents(APP.DS."Config".DS."VERSION"));
define('CDN', Configure::read('CDN.path').VERSION.'/'); /* the trailing slash is important */

Here's how that might work as a LESS config file:

@VERSION: '1.0.0';

@import 'cdn';
@import 'version';
@CDNURL: '@{CDN}@{VERSION}/';
 
button {
    background-image: url('@{CDNURL}img/button.png');
    &:hover {
        background-image: url('@{CDNURL}img/button_hover.png');
    }
}

As you can see, you can also choose to use the CDN variable as a file, environment variable or whatever would work best for you. The principles are mostly the same, all will achieve the desired end result of incorporating the CDN URL (and version number) into all of our external resources.

CDN, FTW!

Hopefully this takes the scare out of setting up a CDN. It can certainly appear to be a daunting task and there absolutely are advanced settings that can get you lost in the weeds. The initial setup is what most of us need anyway and the benefits are profound when it comes to performance and user experience, among a host of other things (pun intended).

The truth is that it gets even simpler if you manage content on your site with a popular CMS like WordPress, where there are plugins galore to streamline the process even further.

Even if we do not have the luxury of plugins, setting up a CDN can still be pretty straightforward, even for the least hosting-oriented of us. The setup is the largest hurdle and we were able to break that down into three basic steps. All else flows nicely from there.