Add trailing slashes in Rails without breaking everything
Tags: rails, ruby
Published: 2023-02-23 01:31:00

Adding trailing slashes to URLs in Ruby on Rails is harder than one might expect. Rails is an opinionated framework, and the framework believes that URLs without trailing slashes are preferable to URLs with trailing slashes.

There are several foot-guns when setting up trailing slashes in Rails, but this post will show you how to get it right.

## Before we start: what's a trailing slash?

A trailing slash is a forward slash at the end of a URL, like `https://mywebsite.com/posts/`.

## Configuring Rails URL helpers to generate trailing slashes

On a default Rails project, the URL helpers don't generate a trailing slash. For example, if you scaffold a `Posts` model, the `posts_path` helper will return `"/posts"` without a trailing slash.

To generate trailing slashes, navigate to `config/application.rb` and add `routes.default_url_options[:trailing_slash] = true` to your `Application` class.

```rb
require_relative "boot"
require "rails/all"

Bundler.require(*Rails.groups)

module ExampleApp
  class Application < Rails::Application
    config.load_defaults 7.0

    # ADD THE FOLLOWING LINE:
    routes.default_url_options[:trailing_slash] = true
  end
end
```

After making this change, you'll need to **restart your application** for it to take affect. All URL helpers will now include the trailing slash, causing `posts_path` to now return `"/posts/"`.

_**NOTE:** you may see other posts that suggest adding `config.action_controller.default_url_options = { trailing_slash: true }` to your `Application` class instead of the snippet above. Don't do this: the trailing slashes won't work in your test suite._

## Redirect to the trailing slash

Both `/posts` and `/posts/` will resolve to the same page in Rails, which is bad for SEO. Ideally, each page should only have a single URL. When a user navigates to `/posts`, we would like to redirect them to `/posts/`.

We _could_ append a trailing slash to the end of every URL that doesn't already end with one. However, if we did that, trailing slashes would be added to JSON requests like `/posts.json/`. File names shouldn't have trailing slashes, so we'll want to account for this.

In `app/controllers/application_controller.rb`, add the following code:

```rb
class ApplicationController < ActionController::Base
  before_action :force_trailing_slash

  private
  def force_trailing_slash
    return if file?
    return if trailing_slash?

    url = url_for \
      request.path_parameters
        .merge(request.query_parameters)
        .merge(trailing_slash: true)

    redirect_to url, status: :moved_permanently
  end

  def trailing_slash?
    URI(request.original_url).path.ends_with? '/'
  end

  def file?
    URI(request.original_url).path.split("/")[-1]&.include? '.'
  end
end
```

Thank you to Paweł Gościcki for his [Stack Overflow solution](https://stackoverflow.com/a/66372604/7759523) that inspired the code above.

## Custom error pages

If we launch this code as-is and your app has custom error pages configured, navigating to a non-existent page without a slash like `mywebsite.com/no-page-here` will redirect to `mywebsite.com/404/`. Ideally, error pages shouldn't redirect at all: it would be preferable to preserve the original URL.

While configuring custom error pages in Rails is outside of the scope of this post, we can prevent error pages from redirecting by skipping the `:force_trailing_slash` before action on the error controller.

```rb
class ErrorsController < ApplicationController
  # ADD THE FOLLOWING LINE:
  skip_before_action :force_trailing_slash

  def not_found
    render(:status => 404)
  end

  def internal_server_error
    render(:status => 500)
  end
end
```

If you'd like to learn to configure custom error pages in a Rails app, take a look at the article on [dynamic Rails error pages by Matt Brictson](https://mattbrictson.com/dynamic-rails-error-pages).

## A word of caution

Unless you have a compelling reason to have trailing slashes in your Rails app URLs (such as preserving legacy URLs), you will probably be better off accepting the Rails defaults. Ruby on Rails becomes very tedious to work with when you fight its opinions, and this can dramatically slow the process of building your app.

Leave a comment below to let me know if this post missed something or if you approach trailing slashes in your Rails app another way.