Problems with CDN-hosted fonts and CORS headers? Here’s a workaround

The problem

Using a CDN (Content Delivery Network) to serve your Rails assets can go a long way in speeding up your site and cutting down on bandwidth costs, but it does comes with added complexity when serving fonts.

In order to use a font hosted on a different domain, your font should be returned with the appropriate CORS headers, otherwise the browser will refuse to use them.

We always seem to spend a while getting this all set-up with Cloudfront (which is really good at caching) – even when following tested recipes – but we wanted a workaround without CORS headers so that clients and QA could test sites in development with the proper fonts in place.

The solution

Firstly, if you’re using a free font (e.g. from Google Fonts), you should have no problem. For other free fonts – like Font Awesome – there are several free CDNs you can slot into your <head> tag.

Otherwise, you’ll need to customise the config.asset_host setting for your Rails application. As well as taking a URL string, you can also set this to a Proc, which will be evaluated for each asset when requested (i.e. when using one of the asset pipeline URL/path helpers).

With this in mind, we can segregate CSS/font assets from other assets – setting our domain as the host for these assets, whilst leaving other assets (javascripts, images) with a CDN URL:

  NON_CDN_ASSETS_REGEX = /\.(css|woff|woff2|ttf|otf|eot)/
  FONT_FILE_REGEX      = /webfont\.svg/i

  ASSET_HOST_PROC = proc {|source|
    if File.extname(source) =~ NON_CDN_ASSETS_REGEX || source =~ FONT_FILE_REGEX
      # Your domain name
      "https://my-domain-name.test"
    else
      # The domain of your CDN
      "https://abcdef1234.cloudfront.net"
    end
  }

We have to be careful to distinguish SVGs, since they could either be fonts or images. In practice, we’ve found that the filename is sufficient (see FONT_FILE_REGEX above) to distinguish fonts, since SVGs generally only come from the asset pipeline (and not user uploads), and we can thus be confident that all SVG filenames are under our control.

Since Sprockets caches individual files (to speed up asset precompilation), you may find you have to change the CSS/Sass file that includes the @font-face statement(s) – or clear your cache – in order for the correct URL to the font file(s) to be generated.

One last gotcha to be aware of is that domain matching for CORS is exact – in other words, cross-domain restrictions apply even between subdomains. This is why both fonts and the CSS file(s) that refer them have to come from the same exact domain.

Conclusion

I hope this workaround helps while getting everything set-up on your app. Now, I’m off to do battle with rack-cors

Photo by Daniel Ullrich on Flickr