How to provide a local fallback for Font Awesome if CDN fails?

The problem is that I’m pretty sure it’s impossible to check if CSS is effectively added to a page via PHP: CSS is parsed by the browser, so client side, and has completely no effect on server side.

Of course, in PHP it’s possible to check if CDN is responsive or not…

Option 1

Send a request and if it responds with HTTP status 200, use it. Something like:

function font_awesome_css() {
    $url="http://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css";
    $cdn = wp_remote_get( $url );
    if ( (int) wp_remote_retrieve_response_code( $cdn) !== 200 ) {
        $url = get_template_directory_uri() . '/css/font-awesome/css/font-awesome.min.css';
    }
    wp_enqueue_style( 'font-awesome', $url, false );
}

that results in 2 HTTP requests, one for the check, the second for embedded CSS: really bad.

Option 2

function font_awesome_css() {
    $url="http://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css";
    $cdn = wp_remote_get( $url );
    if ( (int) wp_remote_retrieve_response_code( $cdn ) === 200 ) {
        $css = wp_remote_retrieve_body( $cdn );
        add_action( 'wp_head', function() use( $css ) {
            $absolute = "//netdna.bootstrapcdn.com/font-awesome/4.0.3/fonts/";
            $css = str_replace( "../fonts/", $absolute, $css );
            echo '<style type="text/css">' . $css . '</style>';
        } );
    } else {
        $url = get_template_directory_uri() . '/css/font-awesome/css/font-awesome.min.css';
        wp_enqueue_style( 'font-awesome', $url, false );
    }
}

This is even worse:

  • It ruins the wp_enqueue_style workflow: if a plugin adds Font Awesome, it will be added 2 times.
  • Number of HTTP requests is the same, however normally the 2 requests run in parallel, so in this way the PHP generation of page slows down because it needs to wait for the first request response.
  • This also prevents the browser from caching the CSS, so if you use the same style in different pages, you force CDN request on every page visited. When using the normal workflow, the pages after first CSS are taken from cache.

So, really, don’t do this at home.

What really matters is that using PHP you can check CDN request, but not verify CSS, so all your efforts end up in worse performance, instead of better.

Sincerely, if yours is a public theme, I suggest you use only the local copy, providing users a way to choose a CDN:

if ( ! function_exists( 'font_awesome_css' ) ) {
    function font_awesome_css() {
        $_url = get_template_directory_uri() . '/css/font-awesome/css/font-awesome.min.css';
        $url = apply_filters( 'font_awesome_css_url', $_url );
        wp_enqueue_style( 'font-awesome', $url, false );
    }
}

So users can completely override the function using a child theme, and can also use the 'font_awesome css_url' filter to change the URL.

Also consider that some high-end hosting providers automatically convert local assets to CDN ones, and there are plugins that allow CDN all the things; this is the reason a public theme should not use CDN at all.

If the theme is for yourself, then make a choice. Consider that most famous CDNs have a very low % of down time (and bootstrapcdn is one of the most reliable, according to cdnperf.com). I’m pretty sure your hosting has a downtime % greater than bootstrapcdn, so people have more probability to not see your site at all, than see it with broken icons.

The dirty way

As said, PHP can’t check CSS, because CSS rendering happens client side, but you can use client side check: JavaScript.

First embed CSS using CDN:

function font_awesome_css() {
    $url="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css";
    wp_enqueue_style( 'font-awesome', $url, false );
} 

After that, add some JavaScript to your footer:

/*
Normally the JS should be properly enqueued and the URL
passed via wp_enqueue_script, but this is a proof of concept,
more than real code.
*/
add_action( 'wp_footer', function() {
    $cssurl = get_template_directory_uri() . '/css/';
    ?>
    <span id="facheck" data-cssuri="<?php echo $cssurl; ?>" class="fa" style="display:none">
    </span>
    <script>
        jQuery(document).ready(function($) {
            var $check = $('#facheck');
            if ( $check.css('fontFamily') !== 'FontAwesome' ) {
                // Font Awesome not loaded!
                // Remove current CSS link
                $('#font-awesome-css').remove;
                // Add the local version
                var local="<link rel="stylesheet" type="text/css" href="" +
                    $check.data('cssuri') + // This is the theme CSS folder URL
                    'font-awesome/css/font-awesome.min.css" />';
                $('head').append( local );
            }
        });
    </script>
    <?php
});

This code runs when the page is loaded and checks if the invisible span added to footer with class ‘fa’ has the font-family property set to ‘FontAwesome’. This is set by Font Awesome, so if it isn’t true, it means that CSS is not loaded. If it happens, the code uses JavaScript to append the local CSS to head.

(To test this code you can embed via wp_enqueue_style a wrong CDN URL, and see what happens)

So, in the rare case a CDN is not available, all the styles will be shown as expected (for some milliseconds users will see ‘broken’ CSS icons, because CSS is added after the page loads).

Now, considering that CDNs are very reliable, is it worth doing this hack for the < 1% of people that will see broken icons? Answering this question is left to you.

Leave a Comment