SEO URLs in Grails

Grails is a language built on the principle of convention over configuration. This is a really fancy way of saying: “let’s not reinvent the wheel, but rather concentrate on getting shit done”.

As a little experiment, I wrote this blog (the one you’re reading) in Grails from scratch with little effort. Unfortunately, given the nature of Grails, I had a certain requirement that Grails didn’t provide out of the box - SEO-friendly URLs.

This problem is nothing new - hundreds of clever people have written fantastic write-ups about how to achieve this. As a proponent of “path-of-least-resistance-programming”, I was in search of a quick-and-easy fix so I could continue with other work.

So, what’s the issue? Grails opts for the URL mapping convention of: controller/action/id (example: blog/view/1), whereas what we really want is something like “posts/my-summer-holiday-2014”.

The first thing I did was have a look at the vast repository of plugins that readily incorporate into Grails, but the plugin I was after was last updated 3 years ago and seems to have slipped into Hades or something.

So, I decided to hack it. Here’s what I did:

  • Setup a desired URL mapping in UrlMappings.groovy, like so:


class UrlMappings {

{gfm-js-extract-pre-1}

}

This will map incoming requests with the pattern of "/posts/?$title" to “blog/view/$title” (notice that the standard ID is being replaced by $title).

  • Next, we’ll want to rewire the controller a bit to handle this. For cases where a title is present, we’ll do a stock standard “findBy” query.

This works fairly easy for single word titles, but what about multi word titles? Let’s concatenate the words together using dashes. Before the actual lookup, we just strip out the dashes, as below, and do the lookup normally.


def view() {
def blogPost if (params.id) { blogPost = Blog.get(id) } else if (params.title) { def title = params.title.replaceAll("-", " ") blogPost = Blog.findByTitle(title) } render(view: "post", model: [blogPost: blogPost]) }

  • How do we end up with dashes as placeholders for spaces in the first place? For that, let’s look at how we invoke the URLs from the UI:


{gfm-js-extract-pre-2}

Three things to note here:

First, we are doing what we did in the controller, but in reverse gear (substituting spaces for dashes).

Second, we are using the g:link in a non-conventional way (avert your gaze, Grails elitists) by pointing to a controller that doesn't exist (we merely mapped ‘posts’).

Third, we are also misusing the action attribute by plugging in a standard URL parameter ($title, in our case) in its place.

Call the cops.