meteor: how we use a CDN
We recently added a CDN in front of all of our apps after reading this post by Josh Owens. For our demo app, we saw an 84% reduction in overall load time, so I'd strongly recommend doing this for any production code. Listed below are the steps we used.
First, we created a CloudFront distribution for each app. The non-default settings we applied are:
- Origin Domain Name: Set this to your your app's domain name - myapp.example.com
- Origin ID: This needs to be a unique string - we set it to the same domain name above.
- Viewer Protocol Policy: We use HTTPS Only.
- Compress Objects Automatically: Set this to "yes" to let the endpoint serve a gzipped version of your assets - improves both speed and bandwidth.
Note that we left the default Use Origin Cache Headers because our NGINX config already specifies expires max
for our css and js assets.
After CloudFront finishes creating your distribution, you'll end up with a domain name like a27n0s89ik4xb3.cloudfront.net
.
The key to telling your app how to prefix its bundled assets (serve them from a CDN), is this line:
WebAppInternals.setBundledJsCssPrefix(CDN_URL);
CDN_URL
in our case is https://a27n0s89ik4xb3.cloudfront.net
.
Because we wanted to enforce a few checks around that line, and automatically whitelist the CDN_URL
in our browser policy, we created a package that does something like this:
var parse = Npm.require('url').parse;
var CDN_URL = process.env.CDN_URL || '';
Meteor.startup(function() {
// Return early if URL isn't HTTPS (or if it isn't set).
var isHttps = parse(CDN_URL).protocol === 'https:';
if (!isHttps)
return;
// Add CDN awesomeness - this is the critical line.
WebAppInternals.setBundledJsCssPrefix(CDN_URL);
// Trust the URL in our browser policy (if it's available).
try {
return BrowserPolicy.content.allowOriginForAll(CDN_URL);
} catch (undefined) {}
});
Of course, you can just paste the critical parts into your server
directory if you don't need to reuse the code between projects.
Then all we do is set the CDN_URL
environment variable when we start in production, and we're all set.
One important note - if you serve your own fonts, you'll end up with cross origin issues. Josh describes how to deal with that in his post. We use font data URLs, so that particular issue didn't affect us.