Custom 404 error page with Rails 4

Written . Tagged Ruby on Rails.

This is what I did to get a custom 404 error page on Rails 4, without replacing the default 500 and 422 error pages.

There are other solutions where you just use the router as the exceptions app, but then you have to handle those other errors as well.

It’s very much based on this Gist by Turadg Aleahmad, but with some cleanup and fixes.

Code changes

Remove the default public/404.html to avoid any collisions.

Modify these files like so:

config/application.rb
1
2
3
4
5
6
7
8
9
10
# …

module NameOfMyApp
  class Application < Rails::Application
    # …

    require Rails.root.join("lib/custom_public_exceptions")
    config.exceptions_app = CustomPublicExceptions.new(Rails.public_path)
  end
end
config/routes.rb
1
2
3
4
5
Rails.application.routes.draw do
  match "/404" => "errors#error404", via: [ :get, :post, :patch, :delete ]

  # …
end

Add these files:

lib/custom_public_exceptions.rb
1
2
3
4
5
6
7
8
9
10
11
class CustomPublicExceptions < ActionDispatch::PublicExceptions
  def call(env)
    status = env["PATH_INFO"][1..-1]

    if status == "404"
      Rails.application.routes.call(env)
    else
      super
    end
  end
end
app/controllers/errors_controller.rb
1
2
3
4
5
class ErrorsController < ApplicationController
  def error404
    render status: :not_found
  end
end
app/views/errors/error404.erb
1
<p>Sorry! No such page!</p>

Verify in development

To see the page in development, just visit /404.

If you see the default Rails 404 page, you probably forgot to remove public/404.html.

If you want to make sure it actually works, change config/environments/development.rb to say

1
2
# Do not commit!
config.consider_all_requests_local = false

instead of true.

Just don’t keep that value, since you’ll get less helpful errors in development, and you’ll also disable the /rails/info/properties page with debug info.

Tests

I haven’t been able to figure out a way to do production-style error handling in a single test, so I settled for this:

spec/features/errors_spec.rb
1
2
3
4
5
6
7
8
9
10
require "rails_helper"

describe "404 page" do
  it "is customized" do
    # Haven't been able to get the "show instead of exceptions" thing working in tests, but this at least makes sure the page can render correctly.
    visit "/404"
    expect(page.status_code).to eq 404
    expect(page).to have_content("Sorry!")
  end
end