Recently I wrote about implementing SEO Friendly URLs in ASP.Net MVC and thought I’d take a stab at porting it over to Ruby on Rails.
For this example we’ll be using the same scenario, we have a movie reviews site where we want our movies to have a more descriptive URL such as:
http://example.com/movie/1/star-trek
There are already a ton of tutorials out there for handling this in Rails but everything I looked at formatted it like this:
http://example.com/movie/1-star-trek
While this might not seem like that big of a difference, only one character, it brings with it some problems to keep in mind. First, you have the problem of looking up data: you either have to parse that string apart to find the ID so you can look up records, or you have to look your records up based on some ruby function that combines the ID and SEO-friendly portion, meaning you aren’t getting efficient queries as you have to bring your entire table back to do the lookup in memory. The second issue I have with the above is unless you are doing the first option for looking up records, you run into issues when you need to change the SEO-friendly portion of that URL, it breaks any existing links and search engine ranking your page has, unless you now maintain a history of all URLs that have existed ever and look up in that table when you can’t find a record. You also lose the discoverability and hackability of the URL, where I may know I need ID 42 but I don’t know the SEO-friendly portion. For these reasons I’m going to show an alternative approach similar to my ASP.Net MVC example.
Like in our ASP.Net MVC example, we want to make it so that hitting incorrect URLs (where the ID is correct but the SEO-friendly movie title is not) ww issue a 301 to the correct location to preserve legacy links and search engine ranking when we change, say, “Star Trek” to “Star Trek: The Motion Picture”, and allow our URLs to be discoverable and hackable since changing the ID also automatically fixes the SEO-friendly name for our user.
http://example.com/movie/1/star-trek-the-motion-picture
First we will start off by adding the following method to our Movie model:
def seo_name
self.name.gsub(/'/m, "").parameterize
end
Rails’ built in parameterize method handles this conversion for us, however it isn’t perfect, so I added the removal of apostrophies before running it to prevent problems where something like “Ocean’s Eleven” would become “ocean-s-eleven” with only Rails’ built in parameterize method, and I would prefer “oceans-eleven” by default.
Next we need to get a route to handle this new URL design:
#config/routes.rb
match 'movie/:id(/:seo_name)' => 'movie#show', :as => :named_movie
Now that we have a special route to use, we can make our controller automatically redirect to the correct SEO-friendly URL if it is not supplied or supplied incorrectly. Now since we may have already existing functionality in place, such as xml or json functionality we want to preserve, we only want our logic to affect html rendering only.
def show
@movie = Movie.find(params[:id])
respond_to do |format|
format.html {
unless params[:seo_name] == @movie.seo_name
head :moved_permanently, :location => named_movie_url(:id => @movie.id, :seo_name => @movie.seo_name)
else
render
end
}
format.xml { render
ml => @movie }
end
end
Excellent! Now our controller is automatically correcting requests to use the SEO-friendly URL! To reference this route with link_to so you don’t throw a 301 every time someone clicks something on your site, you can use:
<%= link_to @movie.name, named_movie_url(:id => @movie.id, :seo_name => @movie.seo_name) %>
Thats it! Now we have SEO-friendly URLs that don’t require us to parse apart a string to find the ID or use an inefficient look-up method in our data access!