Series:

  1. Add custom error pages in a Rails app
  2. Style default error pages in a Rails app inside /public folder
  3. DRY custom error pages

Approach #2 stays my favorite one, because it is the least intrusive (lowest quantity of files to change and maintain).

This is an improvement of approach #1, where we add our error pages to our routes.

First, say that you want to handle errors via your routes:

# config/application.rb
config.exceptions_app = self.routes

Add error paths to routes:

# config/routes.rb
  match "/404", to: "errors#not_found", via: :all
  match "/422", to: "errors#unprocessable_entity", via: :all
  match "/500", to: "errors#internal_server_error", via: :all

Controller to handle all errors:

# app/controllers/errors_controller.rb
class ErrorsController < ApplicationController
  def not_found
    render status: :not_found,
           template: 'errors/index'
  end

  def internal_server_error
    render status: :internal_server_error,
           template: 'errors/index'
  end

  def unprocessable_entity
    render status: :unprocessable_entity,
           template: 'errors/index'
  end
end

Error layout file that accepts TailwindCSS:

<!-- app/views/layouts/errors.html.erb -->
<!DOCTYPE html>
<html class="h-full antialiased">
  <head>
    <%= render 'shared/meta_tags' %>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>
    <%= stylesheet_link_tag 'tailwind', 'inter-font', 'data-turbo-track': 'reload' %>
    <%= stylesheet_link_tag 'application', 'data-turbo-track': 'reload' %>
    <%= javascript_importmap_tags %>
  </head>

  <body class="font-sans font-normal leading-normal bg-sky-950 text-gray-200 flex flex-col min-h-screen">
    <main class="grid h-screen place-items-center">
      <%= yield %>
    </main>
  </body>
</html>

One generic template file that is populated by text from an i18n file:

<!-- app/views/errors/index.html.erb -->
<div class='text-center'>
  <h1 class="font-bold text-4xl">
    <%= response.status %>
    <%= action_name.humanize %>
  </h1>
  <p>
    <%= t :message, scope: [:errors, action_name] %>
  </p>
  <%= link_to 'Return to homepage', root_url, class: 'text-blue-500' %>
  <%= image_tag 'errors.png' %>
</div>

I18n file that handles different text based on error message:

# config/locales/en.yml
en:
  errors:
    internal_server_error:
      message: It's not you. Something went wrong on our end.
    not_found:
      message: Sorry, we couldn't find that page.
    unprocessable_entity:
      message: Sorry, we couldn't process that request.

That’s it! 🤠