To CDN or not to CDN? Why do you need to choose?

  • ASP.NET Core
  • .NET
  • Web

The loading time of a page is an important criteria for the user experience. To speed up this one, CDNs (Content Delivery Network) propose to host the static files (CSS, JS, Images) on their servers. This allows to :

  • Reduce response times: CDNs have servers distributed around the world, so there is a server close to the user.
  • Reduce download time: Files used by multiple sites using the same CDN will only be downloaded once. Then they will be placed in the cache, which avoids re-download. This is especially true for commonly used libraries such as jQuery, Bootstrap, FontAwesome, and so on.
  • To reduce the number of requests on our server: it has more time to answer the "real" requests.
  • Reduce the time of sending the request (often negligible): the domain of the CDN is different from that of our site, the browser does not have to send cookies.

On the other hand our application becomes dependent on the CDN. If this one is unavailable, the files necessary for our application can not be downloaded any more and thus the application does not work. In general this case is not acceptable.

For JavaScript and CSS files it is possible to use a CDN and in case of problem to serve files from our site. So when the CDN is not available, the loading time of the page is a bit longer but the web site still works. Let's see how to do that.

JavaScript files

The first step is to add the script with the address of the CDN:

<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>

Then we will check the proper loading of the script. For that we identify an object or a function defined by the script and we check that it exists. If this is not the case, we add a new script tag to the document. This new tag is added only if necessary and points to the file placed on our server. In our case we can verify that the angular object exists:

<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<script>
if (!window.angular) {
    document.write("<script src=\"angular.js\"><\/script>"); // Load local file
}
</script>

Depending on the needs the verification can be more complex, but the principle remains the same.

CSS files

As for JS files we start by adding a link tag pointing to the CDN:

<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" />

Then you have to check that the style defined in the CSS exists. For this we add a tag with a class defined in the CSS file. For FontAwesome we can for example use the class fa:

<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" />

<meta id="FontAwesomeTest" name="x-stylesheet-fallback-test" class="fa" />

We then check that the style applied to this tag is the right one with some lines of JavaScript. For example for FontAwesome we can check that the font-family is well defined to FontAwesome for the class fa:

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" />

<meta id="FontAwesomeTest" name="x-stylesheet-fallback-test" class="fa" />

<script>
var element = document.getElementById("FontAwesomeTest");

var style = document.defaultView && document.defaultView.getComputedStyle ?
    document.defaultView.getComputedStyle(element) :
    element.currentStyle;

if (style && style["font-family"] !== "FontAwesome") {
    document.write('<link rel="stylesheet" href="font-awesome.min.css" />'); // Load local file
}
</script>

It's a bit more complicated than for JavaScript, but it's still quick to put in place.

ASP.NET Core

ASP.NET Core provides what you need to easy add fallbacks:

<link rel="stylesheet"
      href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css"
      asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
      asp-fallback-test-class="sr-only"
      asp-fallback-test-property="position"
      asp-fallback-test-value="absolute" />

<script src="//ajax.aspnetcdn.com/ajax/bootstrap/3.0.0/bootstrap.min.js"
        asp-fallback-src="~/lib/bootstrap/js/bootstrap.min.js"
        asp-fallback-test="window.jQuery">
</script>

It will automatically adds the code shown in the previous sections. So, it's much easier to use.

Security considerations

You cannot trust the CDN provider. They can tamper the files they serve for any reasons (a bug, an attacker gains control of the server, etc.). You can prevent that by using SRI. Subresource Integrity (SRI) is a security feature that enables browsers to verify that resources they fetch (for example, from a CDN) are delivered without unexpected manipulation. It works by allowing you to provide a cryptographic hash that a fetched resource must match.

<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
        integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
        crossorigin="anonymous"></script>

<link href="https://stackpath.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet"
      integrity="sha384-yNuQMX46Gcak2eQsUzmBYgJ3eBeWYNKhnjyiBqLd1vvtE9kuMtgw6bjwN8J0JauQ"
      crossorigin="anonymous">

You can use Content Security Policy to configure your server to mandate that specific types of files require the use of Subresource Integrity. Do this using the require-sri-for directive in your CSP header. For example:

Content-Security-Policy: require-sri-for script style;

Conclusion

As we can see it is very simple to use a CDN without being totally dependent on it. Just add a check for each remote resource. However, be careful to always use the same version of the scripts on the CDN and on your server…

Do you have a question or a suggestion about this post? Contact me on Twitter or by email!

Follow me:
Enjoy this blog?Buy Me A CoffeeDonate with PayPal