This post was extracted and adapted from The Rails and Hotwire Codex.
When something goes wrong in Rails, the user sees a rather boring default error page.
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.
# 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.
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
<%# 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 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.
# ...
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, to level-up your Rails and Hotwire skills!
Top comments (3)
Extremely helpful, thanks!
This is great @ayushn21 !
@ayushn21 I noticed this doesn't work with
ActionController::InvalidAuthenticityToken
look like it's a problem when a request is in a POST method.