Icelab

Precompiled Rails Static 404 and 500 Pages

By Hugh Evans23 Nov 2012

Great looking custom 404 and 500 pages are a hallmark of well-rounded and polished web applications. Everyone has seen Github’s excellent ones, but check out this sweetness designed by the talented @kolber for the recently relaunched The Thousands, for which we built a custom Rails CMS.

There was a really handy trick we used for these static error pages in Rails. We compiled them by augmenting the rake assets:precompile task. This has many benefits. Firstly, it gives you access to digest paths for assets allowing you to use all the asset_tag related helpers in your templates (“templates” you say? We’ll get to that). Secondly, it helps keep these pages looking fresh as they’ll always have the latest CSS, and if you ever reference an asset that no longer exists in the manifest then the precompile task will fail, thus alerting you to the problem that you’d otherwise never notice.

Setting this up is super simple. First you need to create an app/assets/html directory. Then you need a 404.html.erb and a 500.html.erb (only ERB can work). Then a template might look something like this:

<!DOCTYPE html>
<html class="no-js" lang="en">
  <head>
    <meta charset="utf-8">
    <title><%= ENV['APP_NAME'] %> 404</title>
    <%= javascript_include_tag 'modernizr' %>
    <%= stylesheet_link_tag 'application', :media => 'all', :digest => true %>
  </head>
  <body>
    <section id="error">
      <h1>404 Page not found</h1>
      <a href="/" class="return">&larr; Return to Homepage</a>
    </section>
    <script>
      Modernizr.load("#{javascript_path 'application'}");
    </script>
  </body>
</html>

You notice the asset_tag related helpers all work, but you do however require the :digest => true option on stylesheet link tags.

Now, for the pièce de résistance, the rake task that compiles the templates into your Rails public directory. Throw this in lib/tasks:

require 'fileutils'

Rake::Task['assets:precompile'].enhance do
  Rake::Task['assets:precompile_static_html'].invoke
end

namespace :assets do
  desc 'Compile the static 404 and 500 html template with the asset paths.'
  task :precompile_static_html do
    invoke_or_reboot_rake_task 'assets:precompile_static_html:all'
  end

  namespace :precompile_static_html do
    def internal_precompile_static_html
      # Ensure that action view is loaded and the appropriate
      # sprockets hooks get executed
      _ = ActionView::Base

      config = Rails.application.config
      config.assets.compile = true
      config.assets.digest  = true

      env      = Rails.application.assets
      target   = Rails.public_path
      compiler = Sprockets::StaticCompiler.new(
        env,
        target,
        ['404.html', '500.html'],
        :manifest_path => config.assets.manifest,
        :digest => false,
        :manifest => false
      )

      compiler.compile
    end

    task :all do
      ruby_rake_task('assets:precompile_static_html:primary', false)
    end

    task :primary => ['assets:environment', 'tmp:cache:clear'] do
      internal_precompile_static_html
    end
  end
end

This will magically get called whenever you rake assets:precompile. You just need to add the following line of config to your application.rb:

config.assets.paths << "#{Rails.root}/app/assets/html"

One side effect to this; as well as being copied to your public directory, the compiled templates will also exist in your assets directory (in our case that’s S3). This isn’t the end of the world though and if you’re using Heroku you can reuse them as your Heroku platform error pages.

If you liked this article then you might also like my previous one on organising the Rails asset pipeline.

Work with us, we’re good peopleGet in touch