Introducing _jaws

Awesome Jekyll deployment scripts for AWS S3 and CloudFront

_jaws logo

I've been hosting my blog on Amazon's S3 and CloudFront for a while; I also host a couple other static sites using a similar setup. It's a wonderful setup once you have it working. Blazing fast, dirt cheap, and the complicated moving parts are all abstracted away.

The problem was always getting everything up on CloudFront properly. That's why I wrote _jaws: a comprehensive, drop-in, AWS deployment script for Jekyll blogs or other static sites.

In this post I'll walk you through the problems that _jaws solves, and how it solves them.

Problem: Caching and cache-busting linked assets.

You want your truly static assets like CSS, JavaScript, and images to be cached forever. You don't even want the browser to wait for a 304 Not Modified response. This is easy to do with a Cache-Control: max-age=... header ... until you have to change your assets. The fix is to cache-bust using the query string - e.g., /example.js?v=1 - using a version number or a file modification time or something. You can do this per-asset with Jekyll, by having each asset you include have a ?ts={{site.time}} on the end. But this is tedious.

Solution: _jaws finds every script include, stylesheet include, and image, and inserts the current git commit on the end. Everything is versioned based on when the code actually changes, and is permanently cached. We also do a separate s3cmd sync pass to pass a proper Cache-Control header for JS/CSS.

Problem: Separate hostnames.

Admittedly a minor problem. It can be more efficient if CSS, JavaScript and images are on a separate domain. It means these assets can download asynchronously while the HTML continues to load.

Solution: Simple enough, _jaws does a full pass over HTML assets and inserts a fully-qualified domain name on any included JS, CSS, or image.

Problem: Minification.

It's just easier for development to use un-minified JavaScript, CSS, and HTML. But in a production environment, you really want to strip out all that extra weight. Yeah, you could have both minified and un-minified assets and swap between them (for JavaScript), and for CSS you're probably using something like SASS, LESS or Stylus that will generate minified CSS for you. For HTML you're almost certainly not minifying (and you probably don't care). But it's a good optimization, and it's nice to have it just work.

Solution: _jaws includes YUI Compressor and HtmlCompressor, and runs these on every appropriate asset in your project. Nice, tiny assets.

Problem: Compression.

This is actually where things get tricky and interesting. When you're serving text assets, you really want those to be gzipped. Neither S3 nor CloudFront do this for you. You can upload a gzipped file and tell S3 to include a Content-Encoding: gzip header though.

As an extra problem, for things like images, audio, or video files, you almost certainly don't want them to be gzipped, because they're already compressed. The additional compression will just slow things down.

Solution: The trick is to do several different s3cmd sync commands. One for gzipped content, one for non-gzipped-content. _jaws finds all the files that make sense to compress, and gzips them either using gzip -9 or with zopfli (bonus points for extra compression).

Problem: CloudFront Cache Invalidation

Sometimes you need your changes to happen now. Well, bad news. CloudFront won't let you do that. But 5-10 minutes is attainable using a CloudFront invalidation request.

Luckily s3cmd has a --cf-invalidate command that does this for us. So that was easy.

In Conclusion

After all this, we now have a properly compressed, minified, and optimized site, served off an enterprise CDN network ... for less than a dollar a month. And now, with _jaws, you can do this with two lines:

git clone [email protected]:pauljz/_jaws.git
cp _jaws/ _jaws/

Change a couple configuration settings and you're done! For more information and to get started, take a look at:

  • _jaws on GitHub.
  • My post on setting up S3 and CloudFront for static sites.

I also wanted to throw out a special thank-you to Tobias Sjösten whose excellent blog post inspired this whole script.


comments powered by Disqus