Mikael Konutgan


Creating Static Sites in Ruby with Rack

There is a guide for creating static sites with rack on the heroku dev center, it works ok, but it has a few problems. After creating a few static sites and deploying them to heroku and taking different approaches each time, some simple, some complex, I decided to investigate the issue.

Here is the code that you might find in a lot of places around the web, including the aforementioned article:

use Rack::Static, 
  :urls => ["/images", "/js", "/css"],
  :root => "public"

run lambda { |env|
  [
    200, 
    {
      'Content-Type'  => 'text/html', 
      'Cache-Control' => 'public, max-age=86400' 
    },
    File.open('public/index.html', File::RDONLY)
  ]
}

Firstly, urls can get pretty long and you mostly want to serve all files in public anyway, but the main issue is that all requests that don't correspond to a file will take you to index.html, the home page. Also, you don't really want .html displaying in your urls and using File.open in the body doesn't take advantage of all the headers that Rack::Static and threfore Rack::File will set for you.

Here's my approach. I use Rack::Static like so:

use Rack::Static,
  :urls => Dir.glob("#{root}/*").map { |fn| fn.gsub(/#{root}/, '')},
  :root => root,
  :index => 'index.html',
  :header_rules => [[:all, {'Cache-Control' => 'public, max-age=3600'}]]

We get all the files in root, set an index, which will serve e.g. about/index.html when you get about/ (so it also takes care of serving /index.html when we get '/', the root) and set the 'Cache-Control' header to an hour for all files.

After this we need to run a rack app. We should probably return a 404 page now, as none of the files will have been matched.

headers = {'Content-Type' => 'text/html', 'Content-Length' => '9'}
run lambda { |env| [404, headers, ['Not Found']] }

I refactored this code into a tiny gem, so I can use it directly in all my projects. I called it vienna. Now you can create a static site on heroku by putting these two lines into your config.ru.

require 'vienna'
run Vienna

(And I won't have to write any more Rack::Static code again.)

vienna will also serve a 404.html if it exists, instead of just returning 'Not Found' when a file isn't found. Check out the source.

pinbrowser.co runs on vienna now and I've also tested this site that is powered by Jekyll with vienna, even though I'm hosting it on GitHub Pages instead of heroku. it works great.