🔋 Custom (400 / 500) Error Pages in Ruby on Rails → Exception Handler

Custom Rails error pages can be set up relatively simply. This tutorial explains the 3 methods to do it (config.exceptions_app)…


ExceptionHandler is our Rails custom error pages gem…
📹 Introductory video about how create custom 400/500 error pages in Ruby on Rails… 📹

📋 Overview

Publishing a Rails application is a major investment and we’d all love our
apps to be used & enjoyed by as many as possible…

Unfortunately, an impediment to this are Rails’ default error pages

Rails default error pages…

Rails’ default error pages are boring and bland. More importantly, they’re unprofessional and may dissuade users from further engaging with the app.

Hitting an error, you’d expect the following:

These show regardless of framework and technology stack.

The important thing to note is that they are NOT errors in themselves. They are HTML pages which describe the results of erroneous HTTP requests.

Big difference.

Any time you receive data from a web service, it’s done via HTTP. HTTP is TCP/IP on port 80 and is the core of all web sites/apps:

The structure of an HTTP response…

What you see after ANY HTTP request is the body of the HTTP response (pictured above). Even if an erroneous status code is returned, the client/browser will display the HTML payload.

For us, it means that it does not matter HOW that HTML is generated (static or dynamic). All that matters is that it’s present and has the necessary info…

Some of our Custom Error Pages in Rails…

As such, if we’re able to utilize the Rails views/controller stack, we should
be able to create branded error pages which retain your app’s layout.

Fortunately, Rails has a hook just for this called exceptions_app.

This hook is present in the Rails core, and can be changed in one of the environment files (./config/application.rb). It is the key to creating
custom error pages in Rails.

It is used by Rails to create valid HTML for an erroneous HTTP
request (more explained below). ↴

By default, it pulls the error HTML files that Rails has in its public directory.

The key to changing your error pages in Rails is to ensure this hook has a callback which delivers HTML code from your views/layouts. This can be done with 2 methods: self.routes / Controller.action(:x).call(env):

Our gem does this automatically.

Downloaded 94,000+ times, it’s currently the most popular custom Rails 400/404/500 error pages solution for Rails:

In this tutorial, we’ll showcase the gem and explain how to manually
set up custom error pages on Rails…

☑️ How It Works

Default Rails 400/500 error pages are a set of HTML files stored in
the public directory of your application:

Rails’ Default error pages are kept in the ./public folder…

These files are a fail-safe, loaded whenever your application raises an
error. The reason why they’re vanilla HTML (rather than Rails views) is to prevent further issues being incurred by calling them.

As you can see, one of the reasons why they’re so bland is because they contain their CSS directly in the file ↴

Since the files are hard-coded, most people take it as a sign that Rails cannot serve exceptions [dynamically] from its controller/view stack.

The good news is that YES, you can create custom error pages in Rails. You
can use the same CSS, layouts and controllers that you have already…

To understand how to do this, you need to appreciate how it works. It
comes down to something called the middleware stack…

Middleware is a term used to describe a series of scripts used to interface different types of application. For example, apps built for Linux may use middleware to communicate with Windows based apps over a network.

Rails is built on top of Rack — a middleware suite for Ruby which translates HTTP requests into Ruby/Rails transactions. The Rack middleware stack works on all levels to provide a range of functionality for Rails applications:

One of Rack’s middleware scripts is ActionDispatch::ShowExceptions → loaded each time your application raises an exception.

Because Rails is not HTTP, this script is responsible not only for catching exceptions, but also translating them into an HTTP-compatible response:

It is important to note that the middleware, whilst important, is
almost never seen and generally not changed.

Since the middleware acts on the lowest level of Rails applications
(interfacing with HTTP requests), it’s rare you should get involved with it.

You can see Rails’ middleware setup here

Rails’ middleware stack…

ActionDispatch::ShowExceptions is present half way through the stack.

The key is that when considering how to change Rails custom error pages, you’re not changing anything in Rack/Rails itself.

You’re adding your own HTML generation to the exceptions_app hook. Hooks work by relying on a sub-function to provide a return value, which is then used in the main function (similarly to a variable)…

By default, exceptions_app invokes ActionDispatch::PublicExceptions which uses the path_info attribute of the erroneous request to load one of the default HTML files:

This means that if you are able to use your own custom callback in place of ActionDispatch::PublicExceptions, you’ll be able to provide your own HTML responses.

To do this, you need to define a callback and populate config.exceptions_app in ./config/application.rb with it…

There are two types of callback you can use:
self.routes (routes)
YourController.action(:x).call(env) (custom controller)

If you use the routes, Rails will infer the path_info from the
erroneous request, accessing the route as if the request was directed there…

If you use a controller, the action is loaded directly and as such, you only need to consider how you’re going to deliver an appropriate view & layout.

Here’s how…

✅ Overview

There are 3 ways (2 methods) to create custom Rails error pages:

Routes is simplest. You’re able to push the exceptions_app callback directly to self.routes — the routes will infer the path_info and route to the status code of the error. You then catch the request when it hits the routes.

Custom controller builds on top of this to deliver an ActionController response to exceptions (allows you to use your own views/layouts).

Gems automate the custom controller method. They create a custom controller and keep it in a Rails engine. This allows the gem to store models/views etc without polluting the main app…

⚡️ Gem (Exception Handler)

ExceptionHandler was designed in 2014 and quickly gained traction due to its simplicity. The gem is a one-click solution for custom Rails error pages

ExceptionHandler is the most popular rails custom error pages gem…

ExceptionHandler has been downloaded over 94,000 times and is one
of the most widely used, supported & simplest way to create Rails error pages.

With its zero configuration setup, the gem provides anyone with professional error pages for Railsfully compatible on Rails 4 & 5 …

As the gem has been downloaded 94,000+ times, it’s considered
one of the more popular for Rails.

After installation, it automatically overrides the config.exceptions_app
hook and sends any exceptions to our custom controller:

The custom controller is designed to show either your app’s default layout
(40x errors) or our custom exception layout (50x errors).

There is absolutely ZERO work required to set up the gem, making it a favorite of some of the most active Rails developers in the world…

The core functionality of the gem is to change the layout when an error is raised. Unlike using a routes-based method, it gives total control of the flow.

ExceptionHandler is able to serve the same data but in different formats, showcasing 50x errors on a custom exception layout & 40x errors in your own branded view.

The trick is ensuring that 50x errors do NOT call the DB or invoke variables. 40x errors are ONLY for Not Found errors; 50x errors are from the server.

ExceptionHandler is able to provide professionally branded Rails error pages to the following specs…

  • ✔️Works fully on Heroku
  • ✔️ 100% Rails 4 & 5 compatible
  • ✔️ Adds professional exception pages to your Rails applications
  • ✔️ Works in production, staging and development

You can download it here:

ExceptionHandler on RubyGems

🚦 HTTP Status Codes

Before explaining the manual methods, we need to explain
HTTP Status codes and their role in the Rails exception process

Great video about HTTP Status Codes

HTTP status codes are EXTREMELY important when considering Rails’
error responses. Not in their own capacity but due to how they work with HTTP as a whole…

Because the ENTIRE web is based on HTTP, each time you receive an HTTP response, it needs to respond with one of the following status codes ↓

Full list of HTTP Status Codes

Since Ruby is not native to HTTP, Rails has to translate exceptions into
valid HTTP responses. This is why its middleware takes so much pain to associate the exception to an HTTP status code.

The importance of this is that although generating a valid HTTP status code is necessary for getting Rails to work online, it does not define the type of content sent back to the client.

In other words, just because Rails experienced an exception, doesn’t mean that it needs to send back vanilla HTML pages to the client. You can set it up to respond with views/layouts that you already have set up…

The trick is understanding the types of error, and what they mean for handling error responses.

There are 5 types of HTTP status code, 2 of which are erroneous:

EVERY time you receive a response from HTTP, a status code is included…

HTTP only has two error types.

These are the only erroneous status codes that Rails is able to send back to
the HTTP client. It doesn’t matter if your application starts WW3, the only thing that matters to HTTP is a valid erroneous status code was returned…

This means that when considering how to handle Rails errors, you’re really looking at ways to manage the 40x and 50x errors produced by your app:

  • 40x errors mean that content was not found.
  • 50x errors mean that the server had an error.

Neither of these status codes have any bearing on how your application should behave (explained in a second).

What they do mean is that you only need to consider 2 types of response. And in both cases, HTTP still permits the sending of a message body:

HTTP Headers & Message Body…

As such, we’re still able to send back HTML in either case.

The difference lies in how we’re able to do it.

Rails’ default is to load the default HTML files. We want to change this to use our own views/CSS. To do it, all we need to set up a custom controller action and hook it into exceptions_app

💣 Exceptions

To better understand, it’s important to consider the way in which
Rails translates Ruby/Rails exceptions into HTTP-valid errors.

Specifically, each time an exception is raised, it will have a class associated with it. Ruby is an object oriented language, and binds every action to a class.

Rails cannot accommodate for every class raising an exception. Instead, it
takes the most common and associated them to specific HTTP errors
(from Rack::Utils::HTTP_STATUS_CODES)…

You can extend this list by using the following code:

# config/application.rb
"YourCustomErrorClass" => :not_found

Any class that isn’t matched defaults to the 500 Internal Server Error:

Again, this doesn’t matter to Rails.

It’s ONLY for HTTP…

It means that if you’re handling exceptions with a custom implementation, you only need to be concerned with how Rails is handling the generation of the returned HTML.

All the HTTP stuff is taken care of prior to the callback…

The key is to develop a system which takes requests through exceptions_app and forms HTML that works for both types of HTTP error (40x & 50x).

The main difficulty lies in 50x error pages.

Since 50x errors show when Rails encounters an application error, you cannot use the standard view/CSS flow to generate HTML. You need to use a custom layout & ensure that you’re not calling the database at all

Here’s how…

✔️ Manual Implementations

Manually changing the exception handling process simply means putting your own callback into the exceptions_app hook.

How you do this determines what gets shown on screen..

🏁 Routes

A routes based implementation uses self.routes as the callback.

This takes the path_info attribute of the erroneous request object and opens a route from it. This is how most people set up custom error pages.

Unfortunately, due to the fact that the exception request is not passed to the routes, you’re very limited in scope as to what you’re able to do with respects to data, etc…

# config/application.rb
config.exceptions_app = self.routes
# config/routes.rb
%w( 404 422 500 ).each do |code|
get code, controller: :application, action: :error, code: code
# app/controllers/application_controller.rb
def error
render status_code.to_s, status: (params[:code] || 500)
# app/views/application/404.html.erb
# stuff here

🏴 Controller

A custom controller implementation is much simpler.

Instead of sending exceptions to the routes, you invoke a controller action directly. The key is sending the erroneous environment, which can only be done as exceptions_app is a callback.

The important thing to appreciate with a controller is that it requires setting up the views a 500 error layout and

# config/application.rb
config.exceptions_app = ->(env) { ExceptionsController.action(:show).call(env)}
# app/controllers/exceptions_controller.rb
def show
# Specific code
# app/views/exceptions/show.html.erb

⚡️ Gem

Obviously, our recommendation is to use the gem

If you are intent on using a custom solution, you should definitely look to create a custom controller.

This way, you retain full control over the layout/view process. Not only this, but it’s much more concise & extensible (you can create a model to record exceptions in the database if you wanted).

📝Thanks for reading

Please feel free to ask for help below if required.