How can I build SEO-friendly URLs?

If you have an application that has a number of public pages, you probably want the URLs that it publishes to include various keywords in those addresses. This helps with your SEO, as well as making things more readable to humans.

But how do you go about that?

By default Rails uses a convention that it calls “RESTful routing”. For example, if you wanted to look at the document with ID 123, you would go GET /documents/123 – and this would be represented in your routes.rb file as:

This will also add in routes such as GET /documents for listing a number of documents, POST /documents for creating a new document, PATCH /documents/123 for updating document 123 and DELETE /documents/123 for deleting document 123.

You can then nest these routes – for example, if document 123 was in folder 789 you could define the routes as follows:

Now the routes mentioned above would no longer work. Instead we would need to GET /folders/789/documents/123 to access our document.

It is expected that you use this nesting as a security measure – so if you GET /folders/123456/documents/123 it would return a 404 Not Found, because document 123 is in folder 789, not in folder 123456. This “scoping” can be done quite easily using ActiveRecord associations, provided you design your database with this in mind.

But what if your folders and documents have meaningful names – and you want those to be included in the URLs?

To do this, let’s assume both folders and documents have a “title” field on them. We need to make this mandatory – so define the two title columns as “not null” in your database, and add validations to the Folder and Document models.

Then we can tell Rails how we want our models to appear when they become part of a URL. This is done by overriding a method called to_param.

There are a couple of things to note here.

Firstly, there are a lot of characters that are not allowed in URLs, so we need to make sure we strip them out. Luckily, Rails has a helper method just for that purpose – parameterize.

Secondly, whatever we use here will then be passed in to our controller as the params – so it needs to be something that can be used to identify the correct record quickly and easily.

In our case, we have two options – we can either make the title fields unique (add a unique index into the database tables, and a uniqueness validation into the models) – then in the controller we can use @folder = Folder.find_by title: params[:folder_id] and @document = @folder.documents.find_by title: params[:id]. Then our to_param method is defined as simply self.title.parameterize.

But there’s another neat trick we can use – in Ruby the to_i method converts a string into an integer. But if the string has a load of digits at the beginning, then some text, it ignores the text when doing the conversion. So "123-blah-blah-blah".to_i returns “123”. If our to_param method returns the ID first, then the title, we don’t need to make the title unique (which may be important in some applications) and we can still use the table’s primary key for searching (which will be faster in nearly all cases). So our models will end up looking like this:

Our controller can stay unchanged because when the params are converted into integers, all the text will be stripped out. And our URL that we looked at earlier on will become something like: /folders/789-important-documents/123-letter-to-mum – including all those important SEO-friendly terms.

One last thing – once we have set our params up like this, you may wish to avoid Rails’ restful routing convention. For the most part, the convention has been a really good thing – in the early days of Rails, every project did routing differently and it could take a while to understand the structure of an application (I used to get freelance jobs where I would take over someone else’s project and the state of the routes file was often my first glimpse into how well-written the app was). But if you want to avoid it, you can define routes directly by hand in routes.rb as follows:

This will convert our earlier URL into /docs/789-important-documents/123-letter-to-mum – full control over how your application’s addresses look for very little effort.

Do you know what to do but not how it works?

Ever wanted to understand why Rails views work the way that they do? Why variables from your controllers are visible inside your views?

Sign up below to get a free 5 part email course on how Ruby on Rails view rendering works and gain a deep understanding of the Rails magic.

We will send you the course, plus the occasional update from this web-site. But no spam, we promise, and it's easy to unsubscribe