Custom error pages in Rails
Tags: rails, ruby, webdev
Published: 2022-10-06 13:07:10

_This post was extracted and adapted from [The Rails and Hotwire Codex](https://railsandhotwirecodex.com)._


When something goes wrong in Rails, the user sees a rather boring default error page.

![The default Rails 404 error page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ci885mioj4f88adokea.png)

This page lives in the `/public` folder and hence isn't rendered through the Rails stack. 

To jazz up this page a bit, we'll create a controller to render errors so the Rails infrastructure can be used.

## Setting up

A configuration change needs to be made so public facing errors are rendered in development rather than the exception and stack trace.

```ruby
# config/environments/development.rb
require "active_support/core_ext/integer/time"

Rails.application.configure do
  # ...

  config.consider_all_requests_local = false
end
```

Create the controller.

```
$ bin/rails g controller errors --no-helper --no-test-framework
```

This controller will have a single `show` action where we'll extract the error code for the raised exception and render the appropriate view. Error codes `403`, `404` and `500` will have dedicated error pages and we'll fall back to the `404` page for everything else. 

```ruby
class ErrorsController < ApplicationController
  layout "error"

  def show
    @exception = request.env["action_dispatch.exception"]
    @status_code = @exception.try(:status_code) ||
                   ActionDispatch::ExceptionWrapper.new(
                    request.env, @exception
                  ).status_code

    render view_for_code(@status_code), status: @status_code
  end

  private
    def view_for_code(code)
      supported_error_codes.fetch(code, "404")
    end

    def supported_error_codes
      {
        403 => "403",
        404 => "404",
        500 => "500"
      }
    end
end
```

As you can see, we're using a bespoke `"error"` layout as well. These pages will be simple and won't need the usual baggage from the application layout. 

Create the new error layout.

```
$ touch app/views/layouts/error.html.erb
```

```erb
<%# app/views/layouts/error.html.erb %>

<!DOCTYPE html>
<html>
  <head>
    <%= render "layouts/head" %>
  </head>

  <body>
    <main>
      <%= yield %>
    </main>
  </body>
</html>
```

Create the views and fill them in as you wish.

```
$ touch app/views/errors/403.html.erb
$ touch app/views/errors/404.html.erb
$ touch app/views/errors/500.html.erb
```

We need to now tell Rails to use this controller to render errors.

## Exceptions app

Rails provides a hook to render custom errors through a configuration property called `exceptions_app`. This property has to be assigned a [Rack app](https://thoughtbot.com/upcase/videos/rack) which is invoked when an exception is raised.

Every Rails controller action is actually its own Rack application! The Rack endpoint is returned by the `action` method on a controller class.

```ruby
# ...

module MyApp
  class Application < Rails::Application
    config.load_defaults 7.0

    config.exceptions_app = ->(env) {
      ErrorsController.action(:show).call(env)
    }
  end
end
```

Restart your server and then try visiting a page that doesn't exist. Or manually trigger an error in a controller action. You'll see your custom error pages in action!


_If you liked this post, check out my book, [The Rails and Hotwire Codex](https://railsandhotwirecodex.com), to level-up your Rails and Hotwire skills!_