Permanent 301 redirections using a Rack middleware in Rails

It’s a quarter to midnight and you are just about to deploy a new version of a web site that has a totally new hierarchy.  This happens a lot when migrating a PHP site to Rails.  And then you realize that the structure or the URLs is totally different than the one on the old site.

Maybe the old site has a nice ranking in Google and a good reputation or lots of people have bookmarked pages of the site.  You don’t want to make Google and those users unhappy.  So you want to have the new web site send a 301 reponse code (permanent redirect) instead of the famous 404 (page not found).

One way to do this in Apache is to enable the rewrite engine and add some rewrite rules in the web site configuration.  But these rules are not easy to build and it involves changing the Apache config, which is not trivial for some people.

I found a Rack middleware called Rack::Rewrite that can take care of the redirections.  I find this solution interesting because it is web server agnostic (will work with Apache, Nginx, …).  Although the performance is not as good as having rewrite rules at the web server level.

rails 2.x

First, you need install the gem:

gem install rack-rewrite

Then, you want to include a reference to the gem in config/environment.rb:

config.gem 'rack-rewrite', '~> 0.2.0'

Rails 3.x

First, you need to include the gem in your Gemfile:

gem 'rack-rewrite'

Then, we need to load the middleware in the rails stack of our application.  Instead of putting this in the environment.rb file, I prefer to put it in an initializer (config/initializers/rack_rewrite.rb) since we will also include the redirection rules in this file:

Rails 2.x

ActionController::Dispatcher.middleware.insert_before(Rack::Lock, Rack::Rewrite) do
  r301 '/fr/rates_and_packages', '/fr/services-et-tarifs'
  r301 '/en/rates_and_packages', '/en/services-and-rates'
  r301 '/fr/information', '/fr/renseignements/horaire-et-coordonnees'
  r301 '/en/information', '/en/informations/business-hours-and-location'
  r301 %r{\A/index.php}, '/fr'
  r301 %r{/en/index.php}, '/en'

Rails 3.x

YourApplicationName::Application.config.middleware.insert_before(Rack::Lock, Rack::Rewrite) do
  r301 '/fr/rates_and_packages', '/fr/services-et-tarifs'
  r301 '/en/rates_and_packages', '/en/services-and-rates'
  r301 '/fr/information', '/fr/renseignements/horaire-et-coordonnees'
  r301 '/en/information', '/en/informations/business-hours-and-location'
  r301 %r{\A/index.php}, '/fr'
  r301 %r{/en/index.php}, '/en'

Remember to include your application namespace in the previous code snippet.

As you can see, the first rules look for a perfect match. The last two rules apply a regular expression to the request URI. This is really cool when a bunch of old URLs have a similar structure and share the same redirection URL.  There is a rewrite command that allows to transform the URL and pass it down the Rails stack.  See the documentation for more options.

Now, how do you find out what are the old URLs you need to redirect?  Well, you can go on the old site and manually crawl the site.  But I prefer to use a simple ruby crawler called Anemone.

gem install anemone

Then write some ruby code and run it.

require 'rubygems'
require 'anemone'

Anemone.crawl('') do |anemone|
  anemone.on_every_page do |page|
    puts page.url

It will give you a list of all the URLs found on the given site. Though it does not work if the site is using Flash menus (normally, such sites should have regular navigation menus somewhere on the page for SEO purposes, but I’ve seen some sites that don’t).

That’s it. A simple way to generate redirection codes. There is a more complete solution named Refraction that you can use as well, but I am satisfied with the straight forward Rack::Rewrite solution.

Here you go, hoping it helps you in your Rails project.


2 thoughts on “Permanent 301 redirections using a Rack middleware in Rails

  1. Pingback: Montreal Tech Watch » Best of, Part 2

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s