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.