Custom Error Pages in Rails — You’re doing it wrong.

If you are a developer, chances are that you had to customize your error pages. Unfortunately, the famous “Uh-oh, something went wrong” and “Page Not Found” errors are inevitable, even at the most stable web sites. Your users landing to these pages might get upset, so you certainly want to have a nice looking error page. See Slack’s 404 page, aren’t you happy that “you’ve found yourself in a weird place”? :)

Slack’s 404 page, see https://slack.com/404

We at Dockbit, also wanted to customize error pages in one of our Rails apps. When I googled for “rails custom error pages”, I was surprised to find 220K results on this topic! So, why yet another post you may ask? Well, most of the examples I’ve found are actually trying to make static error pages dynamic and I think that’s wrong.

The most common solution being suggested is to define your own exception app (config.exceptions_app), route it to some controller and voilà: you can now use your Rails helpers and layouts in error pages. That’s exactly what I dislike about it. You’re basically in the middle of an error, which might have been caused by one of those helpers or partials, and yet trying to dynamically render it?! What if an error page has an error? Moreover, since most of the Rails apps are running behind Nginx (or similar), your Web server won’be able to access them when Rails itself is down, because they are dynamic. I have also seen a number of discussions that this solution has side effects on external exception handling apps, such as AirBrake, though I haven’t verified it.

So, here is what we do to keep our Rails error pages static, but still take advantage of “digested” stylesheets and links to other assets.

  1. Create a directory under app/assets/html, where we will store our asset pipeline precompiled static pages.
  2. Open config/initializers/assets.rb and add these lines:
Rails.application.config.assets.paths << Rails.root.join('app', 'assets', 'html')
Rails.application.config.assets.register_mime_type('text/html', '.html')

3. Install NonStupidDigestAssets gem by Alex Speller, which will make asset pipeline generate non-digest version of our static pages:

gem 'non-stupid-digest-assets'
bundle install

4. Create config/initializers/non_digest_assets.rb and add html files to the non-digest whitelist:

NonStupidDigestAssets.whitelist = [/.html$/]

5. Start creating your custom error pages. Here is one simple example of assets/html/404.html.erb:

<!DOCTYPE html>
<html lang='en'>
<head>
<title>You've found a Glitch!</title>
<meta charset='utf-8'>
  <link rel='shortcut icon' 
href='<%= image_url('favicon.ico') %>'
type='image/x-icon'>
  <%= stylesheet_link_tag 'application', media: 'all' %>
</head>
<body id='error-page'>
<%= image_tag('logo.svg') %>
<h1>You've found yourself in a weird place.</h1>
</body>
</html>

Now, whenever you rake assets:precompile, you will find 404.html under public/assets, which you can preview at http://localhost:3000/assets/404.html. Finally, you would need to configure Web server to find the error pages and handle errors happening “outside” Rails stack. For instance for Nginx and our 404 example, you can use something along these lines:

root /myapp/current/public;
error_page 404 /404.html;
location /404.html {
internal;
root /myapp/current/public/assets;
}

That’s it folks! I hope you’ve found this post useful. I’ll leave you with the 404 page that made my day: https://berniesanders.com/404

Update 1:

I forgot to mention that I also deleted default pages from public/ and have a little Capistrano task that copies them from public/assets to public/ on deploy, so that Rails can find them.