Originally Published: 2021-09-19
For PodQueue, I wanted to generate static error pages that used the same Rails layout and branding as the main site, and while there are many approaches floating around for this, none quite worked exactly the way I wanted them to (or at all) with Rails 6 and Heroku.
The most promising approach I found was this one by Ryan Schultz, however, it was written in 2013 and I had issues with getting the
ActionDispatch::Integration::Session-based page rendering it uses to work. It still forms the basis for my solution though, which is to add a new
rake app:static task to generate the static pages.
So in my Rails app’s
lib/tasks/app.rake I have:
You’ll notice that a major difference here is that I’m using
ApplicationController::Renderer to render the pages into static files, since this seems to be the preferred way of doing it now (specifying the hostname and HTTPS, just in case). There’s also some extra work I’m doing at the end for the Heroku-specific error pages
maintenance-mode.html, which get pushed to an S3 bucket. Since the Heroku error pages also get served via an
<iframe>, I use Nokogiri to rewrite relative asset paths into absolute URLs. (If you’re not using Heroku, you can ignore all that.)
The “regular” error pages just get written out to the
public directory and served normally. The
app:static task is hooked onto asset precompilation so that it happens afterwards every time I run a deploy - this is done by using
enhance in my main
Rakefile like so:
Rake::Task['assets:precompile'].enhance do Rake::Task['app:static'].invoke end
You may have also noticed that I said I wanted to use the main application layout for my errors, but I’m using
layout/errors in the task. This is because I also do need some error-page-specific logic while still inheriting from the main layout (in
content_for :error_page lets us tell from any other layout or view if we’re inside the static error page rendering context, and the
:stylesheets content is used in the main layout to have error-page specific CSS.
One important thing for the way I use this is that I also use Devise for authentication and
ApplicationController.renderer doesn’t have access to Devise’s
Warden::Proxy instance by default, so if you try to use any Devise methods like
current_user, you’ll get the exception:
Devise could not find the `Warden::Proxy` instance on your request environment
So for any layout/view path triggered by our static error pages, we need to guard any Devise calls with a check on
<% if (!content_for?(:error_page)) && user_signed_in? %>.
I’m also using the
high_voltage gem to handle my error pages, but as long as you have a route for your error pages you should still be able to use this technique. Since the error pages use compiled asset slugs that will change constantly, I don’t keep the generated pages in version control.
Thanks for reading, and I hope this approach helps you navigate the confusing landscape of Rails static error pages!