Import maps with Allow Browser in Rails

Page content

Introduction

In Rails 7.2, Rails introduced a new feature called Allow Browser which basically blocks any old browser which doesn’t support the following features: webp images, web push, badges, import maps, CSS nesting, and CSS :has. It uses the user agent string to detect this (yuck?), but understandably as feature detection cannot be done from the backend.

It does this because Rails 7/8 uses import maps (<script type="importmap">) by default, among other features. And import maps is the main thing I am keen on. Without allow_browser, the page will load but users will face mysterious errors as the JS code will completely fail to execute.

Rails is Omakase, but this isn’t always what we want.

Problem

Any browser that doesn’t meet the criteria (detected via user agent), will see this sad page:

Not acceptable indeed!

Not acceptable indeed!

In a new Rails 8 project, this is what you get by default which enables this behavior:

class ApplicationController < ActionController::Base
  # Allow only browsers natively supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has
  allow_browser versions: :modern
end

Which is great for developer experience, but not so great for users:

406 failures from users with old browsers.

406 failures from users with old browsers.

It puzzles me that even though 94% of browsers are “modern”, I still get a huge number of 406 requests.

Solution

For an upstart website, I simply cannot afford to lose any users.

In my application, the only modern feature I care about is import maps. I do not want to go back into JS bundling with jsbundling-rails or vite. Almost went back there but gave up during setup.

This is what you can do instead. Use a polyfill, but only load the polyfill on older browsers! To do this, we will leverage allow_browser to our advantage.

First, define a method and pass it to :block. The method should simply set a boolean to true.

class ApplicationController < ActionController::Base
  allow_browser versions: :modern, block: :handle_outdated_browser

  private

  def handle_outdated_browser
    @inject_importmap_shim = true
  end
end

Then, in your application.html.erb, right before the import map tag, use the boolean and only tax the users from old browsers with the amazing shim from es-module-shims:

<% if @inject_importmap_shim %>
<script async src="https://ga.jspm.io/npm:es-module-shims@2.0.10/dist/es-module-shims.js"></script>
<% end %>
<%= javascript_importmap_tags %>

Deploy this, and test it out on any old browser with browserling, they give 3-minute browser sessions for free!