2012-10-10

Why Rails -> Static HTML is good

So you want to build a static website - a bunch of HTML files, some CSS, images - something you can host anywhere. Yet, you want to reuse your code, whenever you can - shared navigation, partial blocks, etc. It would also be nice to have some basic content management. You could build it with Rails and export a bunch of static html files. My website and this blog is built that way, and it's pretty simple to do. You can also use this technique with other frameworks, like Django.

The Quick Formula

  1. Make HTML friendly routes
  2. Run in production mode on local machine
  3. Grab the static version using wget
  4. Deploy HTML files to your hosting server

HTML Friendly Routes

There is only one rule - your routes must end with .html, so your URLs will look like blog.html. Here is an example route:

#in `routes.rb`:
get "blog.html" => "posts#index", :as => :blog

#in views:
<%= link_to "Blog", :blog_path %> <!-- will generate <a href="/blog.html">Blog</a> -->

If you don't want your URLs to have that .html extension, you can do this folder trick:

#in `routes.rb`:
get "blog/" => "posts#index", :as => :blog
get "blog/index.html" => "posts#index"   

#in views:
<%= link_to "Blog", :blog_path %> <!-- will generate <a href="/blog/">Blog</a> -->

#in `application.rb`, for generating urls with trailing slash:
config.action_controller.default_url_options = { :trailing_slash => true }

With the example above, wget will create a folder named blog/ and put index.html inside, and /blog/ URL will be accessible.

Grabbing The Site

Make sure you are running Rails server in production mode before grabbing. Why production? In development mode Rails appends query strings with versions to static assets to avoid browser cacheing them, and wget will be stupid enough to save files named application.css?body=1. You will not be able to deploy these without renaming them. It's much simpler to run Rails in production instead:

rake assets:clean
rake assets:precompile
# Rails may want you to have a synced production database. SQLite works great.
RAILS_ENV=production db:migrate
rails server -e production

Now, go to someplace clean (i.e.: temp) and run this:

wget -m localhost:3000

It will recursively download your whole site and put it into localhost:3000/ directory. You won't be able to see it properly right from file system because of relative URLs, but try deploying that and it will work like a charm. For easier deployment tar the files:

cd "localhost:3000"
tar -cf website.tar *

Place website.tar in document root of your server and extract it:

tar -xf website.tar

It should work. If you want to serve those files from subdirectory, i.e.: example.com/subdir/, you will have to scope all your paths in routes.rb and set ENV['RAILS_RELATIVE_URL_ROOT'] = "/subdir" in your environment.rb. I haven't tried it though.

Bonus: Dead Simple Local Admin Mode

To manage my content, I found this simple trick to work amazingly well - generate scaffolds to manage your content and add this around links that lead to content manipulation:

<% if params[:admin] %>
  ... links to "new", "edit", "delete" content ...
<% end %>

Then simply run Rails in production mode and manually add ?admin=1 to URLs where you want admin features to appear. Since you're exporting static HTML, there is no security to concern for. Just don't forget to change this method if you ever decide to deploy your Rails app instead of exported static content.

Next steps

To make it even easier, automate your deployment with Capistrano. I found it useful to have a two step deployment - first generate and deploy a test version of the static site, see if everything is OK and then finish the deployment by wiping out production directory and moving test files there. And don't forget to add your production database to source control, assuming you went with SQLite.

Ruby on Rails