How to fallback to local stylesheet (not script) if CDN fails

HtmlJqueryCssJquery MobileCdn

Html Problem Overview


I am linking to the jQuery Mobile stylesheet on a CDN and would like to fall back to my local version of the stylesheet if the CDN fails. For scripts the solution is well known:

<!-- Load jQuery and jQuery mobile with fall back to local server -->
<script src="http://code.jquery.com/jquery-1.6.3.min.js"></script>
<script type="text/javascript">
  if (typeof jQuery == 'undefined') {
    document.write(unescape("%3Cscript src='jquery-1.6.3.min.js'%3E"));
  }
</script>

I would like to do something similar for a style sheet:

<link rel="stylesheet" href="http://code.jquery.com/mobile/1.0b3/jquery.mobile-1.0b3.min.css" />

I am not sure if a similar approach can be achieved because I am not sure whether the browser blocks in the same way when linking a script as it does when loading a script (maybe it is possible to load a stylesheet in a script tag and then inject it into the page) ?

So my question is: How do I ensure a stylesheet is loaded locally if a CDN fails ?

Html Solutions


Solution 1 - Html

One could use onerror for that:

<link rel="stylesheet" href="cdn.css" onerror="this.onerror=null;this.href='local.css';" />

The this.onerror=null; is to avoid endless loops in case the fallback it self is not available. But it could also be used to have multiple fallbacks.

However, this currently only works in Firefox and Chrome.

Update: Meanwhile, this seems to be supported by all common browsers.

Solution 2 - Html

Not cross-browser tested but I think this will work. Will have to be after you load jquery though, or you'll have to rewrite it in plain Javascript.

<script type="text/javascript">
$.each(document.styleSheets, function(i,sheet){
  if(sheet.href=='http://code.jquery.com/mobile/1.0b3/jquery.mobile-1.0b3.min.css') {
    var rules = sheet.rules ? sheet.rules : sheet.cssRules;
    if (rules.length == 0) {
      $('<link rel="stylesheet" type="text/css" href="path/to/local/jquery.mobile-1.0b3.min.css" />').appendTo('head');
    }
 }
})
</script>

Solution 3 - Html

I guess the question is to detect whether a stylesheet is loaded or not. One possible approach is as follows:

  1. Add a special rule to the end of your CSS file, like:

    #foo { display: none !important; }

  2. Add the corresponding div in your HTML:

  3. On document ready, check whether #foo is visible or not. If the stylesheet was loaded, it will not be visible.

Demo here -- loads jquery-ui smoothness theme; no rule is added to stylesheet.

Solution 4 - Html

Assuming you are using the same CDN for css and jQuery, why not just do one test and catch it all??

<link href="//ajax.googleapis.com/ajax/libs/jqueryui/1/themes/start/jquery-ui.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jqueryui/1/jquery-ui.min.js"></script>
<script type="text/javascript">
    if (typeof jQuery == 'undefined') {
        document.write(unescape('%3Clink rel="stylesheet" type="text/css" href="../../Content/jquery-ui-1.8.16.custom.css" /%3E'));
        document.write(unescape('%3Cscript type="text/javascript" src="/jQuery/jquery-1.6.4.min.js" %3E%3C/script%3E'));
        document.write(unescape('%3Cscript type="text/javascript" src="/jQuery/jquery-ui-1.8.16.custom.min.js" %3E%3C/script%3E'));
    }
</script>

Solution 5 - Html

this article suggests some solutions for the bootstrap css http://eddmann.com/posts/providing-local-js-and-css-resources-for-cdn-fallbacks/

alternatively this works for fontawesome

<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
<script>
    (function($){
        var $span = $('<span class="fa" style="display:none"></span>').appendTo('body');
        if ($span.css('fontFamily') !== 'FontAwesome' ) {
            // Fallback Link
            $('head').append('<link href="/css/font-awesome.min.css" rel="stylesheet">');
        }
        $span.remove();
    })(jQuery);
</script>

Solution 6 - Html

You might be able to test for the existence of the stylesheet in document.styleSheets.

var rules = [];
if (document.styleSheets[1].cssRules)
	rules = document.styleSheets[i].cssRules
else if (document.styleSheets[i].rules)
	rule= document.styleSheets[i].rules

Test for something specific to the CSS file you're using.

Solution 7 - Html

Here's an extension to katy lavallee's answer. I've wrapped everything in self-executing function syntax to prevent variable collisions. I've also made the script non-specific to a single link. I.E., now any stylesheet link with a "data-fallback" url attribute will automatically be parsed. You don't have to hard-code the urls into this script like before. Note that this should be run at the end of the <head> element rather than at the end of the <body> element, otherwise it could cause FOUC.

http://jsfiddle.net/skibulk/jnfgyrLt/

<link rel="stylesheet" type="text/css" href="broken-link.css" data-fallback="broken-link2.css">

.

(function($){
    var links = {};
    
    $( "link[data-fallback]" ).each( function( index, link ) {
        links[link.href] = link;
    });
    
    $.each( document.styleSheets, function(index, sheet) {
        if(links[sheet.href]) {
            var rules = sheet.rules ? sheet.rules : sheet.cssRules;
            if (rules.length == 0) {
                link = $(links[sheet.href]);
                link.attr( 'href', link.attr("data-fallback") );
            }
        }
    });
})(jQuery);

Solution 8 - Html

Do you really want to go down this javascript route to load CSS in case a CDN fails?

I haven't thought all the performance implications through but you're going to lose control of when the CSS is loaded and in general for page load performance, CSS is the first thing you want to download after the HTML.

Why not handle this at the infrastructure level - map your own domain name to the CDN, give it a short TTL, monitor the files on the CDN (e.g. using Watchmouse or something else), if CDN fails, change the DNS to backup site.

Other options that might help are "cache forever" on static content but there's no guarantee the browser will keep them of course or using the app-cache.

In reality as someone said at the top, if your CDN is unreliable get a new one

Andy

Solution 9 - Html

Look at these functions:

$.ajax({
    url:'CSS URL HERE',
    type:'HEAD',
    error: function()
    {
        AddLocalCss();
    },
    success: function()
    {
        //file exists
    }
});

And here is vanilla JavaScript version:

function UrlExists(url)
{
    var http = new XMLHttpRequest();
    http.open('HEAD', url, false);
    http.send();
    return http.status!=404;
}
if (!UrlExists('CSS URL HERE') {
AddLocalCss();
}

Now the actual function:

function AddLocalCss(){
document.write('<link rel="stylesheet" type="text/css" href=" LOCAL CSS URL HERE">')
}

Just make sure AddLocalCss is called in the head.

You might also consider using one of the following ways explained in this answer:

Load using AJAX

$.get(myStylesLocation, function(css)
{
   $('<style type="text/css"></style>')
      .html(css)
      .appendTo("head");
});

Load using dynamically-created

$('<link rel="stylesheet" type="text/css" href="'+myStylesLocation+'" >')
   .appendTo("head");
Load using dynamically-created <style>

$('<style type="text/css"></style>')
    .html('@import url("' + myStylesLocation + '")')
    .appendTo("head");

or

$('<style type="text/css">@import url("' + myStylesLocation + '")</style>')
    .appendTo("head");

Solution 10 - Html

I'd probably use something like yepnope.js

yepnope([{
  load: 'http:/­/ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js',
  complete: function () {
    if (!window.jQuery) {
      yepnope('local/jquery.min.js');
    }
  }
}]);

Taken from the readme.

Solution 11 - Html

//(load your cdn lib here first)

<script>window.jQuery || document.write("<script src='//me.com/path/jquery-1.x.min.js'>\x3C/script>")</script>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionssnView Question on Stackoverflow
Solution 1 - HtmlJan Martin KeilView Answer on Stackoverflow
Solution 2 - Htmlkaty lavalleeView Answer on Stackoverflow
Solution 3 - HtmlSalman AView Answer on Stackoverflow
Solution 4 - HtmlMike WillsView Answer on Stackoverflow
Solution 5 - Htmldc2009View Answer on Stackoverflow
Solution 6 - HtmlStefan KendallView Answer on Stackoverflow
Solution 7 - HtmlskibulkView Answer on Stackoverflow
Solution 8 - HtmlAndy DaviesView Answer on Stackoverflow
Solution 9 - Htmluser529649View Answer on Stackoverflow
Solution 10 - HtmlBen SchwarzView Answer on Stackoverflow
Solution 11 - Htmlcrazy4groovyView Answer on Stackoverflow