DEV Community

Tyler Smith
Tyler Smith

Posted on

Add trailing slashes in Rails without breaking everything

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

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.

require_relative "boot"
require "rails/all"


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

    routes.default_url_options[:trailing_slash] = true
Enter fullscreen mode Exit fullscreen mode

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:

class ApplicationController < ActionController::Base
  before_action :force_trailing_slash

  def force_trailing_slash
    return if file?
    return if trailing_slash?

    url = url_for \
        .merge(trailing_slash: true)

    redirect_to url, status: :moved_permanently

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

  def file?
    URI(request.original_url).path.split("/")[-1]&.include? '.'
Enter fullscreen mode Exit fullscreen mode

Thank you to Paweł Gościcki for his Stack Overflow solution 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 will redirect to 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.

class ErrorsController < ApplicationController
  skip_before_action :force_trailing_slash

  def not_found
    render(:status => 404)

  def internal_server_error
    render(:status => 500)
Enter fullscreen mode Exit fullscreen mode

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.

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.

Top comments (0)