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.
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:
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 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.
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.
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)