There are a number of different ways to handle custom error pages in rails. Most use rails’s rescue_from method. This approach allows your error pages to be dynamically rendered in response to errors.

The way I see it, if my site has just encountered an error, I want to immediately go into damage control mode. Do I want to risk dynamically generating an error page? No, not at all. There could be an error in the page rendering that will cause another error when I try to render the error page. Not an ideal situation to say the least. It’s much safer to serve up previously generated static content.

While rails does come with a default, static, error page, said page is largely unstyled, and contains no links to help the user get back to what they were doing. I feel that it’s bad enough that I have to display an error to the user; leaving them with nowhere to go, looking at a jarring, or even ugly, error page isn’t going to make them the happy users that we all want.

Lastly, I don’t want to have to remember to keep the site’s error pages up to date with the latest layout and style.

To summarize, I want static error pages, rendered using rails’s ActionView, and I want them updated automatically any time the site’s style or layout changes.

To accomplish these goals I wrote a rake task and a capistrano recipe. The rake task visits the site, collects the rendered error pages, and stores them in the <RAILS_ROOT>/public directory, where rails will automatically look for them when errors occur. Here’s the rake task:

desc "Generate static error pages (404 and 500)"
task :generate_static_error_pages => [:environment] do
  require "console_app"

  urls_and_paths.each do |url, path|
    r = app.get(url)
    if 200 == r
      File.open(Rails.public_path + path, "w") do |f|
        f.write(app.instance_variable_get(:@body))
      end
    else
      $stderr.puts "Error generating static file #{path} #{r.inspect}"
    end
  end
end

def urls_and_paths
  [["/static/404_not_found", "/404.html"],
   ["/static/500_internal_server_error", "/500.html"],]
end

The capistrano recipe simply runs the rake task each time the application is deployed. Here’s the recipe:

desc "Generate static error pages"
task :generate_static_error_pages, :except => {:no_release => true} do
  run "cd #{current_path} ;
       RAILS_ENV=production rake generate_static_error_pages"
end


I’ve instructed capistrano to execute this recipe each time I deploy my application by using one of capistrano’s many handy callbacks:

after "deploy:symlink", "generate_static_error_pages"

The result is that I have consistently styled, static error pages, that are updated automatically each time I deploy my application.