January 20, 2007

We’ve all been there – SEO can be quite a pain in the ass when you’re competing against others for exposure. Turns out, much of the tedious content optimization can be resolved in the development phase, using nice and descriptive URLs.

For example, if you’re reading this part of the post, you’ve most probably clicked some link that takes you to the view page for this article. If you’ll look at the URL, you’ll notice a very consistent structure between the view pages for the different posts, which is (in Symfony routing terms):

/:year/:month/:day/:post_slug

So far so good. It’s a classic routing rule, described in nice detail in the Symfony book. We define a module and action that receives the above mentioned GET parameters and define a Propel Criteria object:

The action’s code is a bit messy, since we’ve assembled the query right in it (in a real setting, I’d go for refactoring the whole thing to a static function within BlogPostPeer that returns a Propel object given the 4 parameters specified. Oh well, but it suffices for the example given.

This solution, however, proves to have shortcomings – one, the criteria needed to fetch the object is pretty complex, since we need to search by date and post_slug. Two, if multiple posts were added on the same day with the same title, only one of them would come through. To improve on this, let’s explore the magical world of URL slug generation (which forms the permalink, if you’ve missed the connection between the two terms).

First, we need an algorithm to generate our slug. A method I use is to have as many humanly readable characters displayed in the URL without them being URL-encoded. To do this, we create a utility class that contains static functions, just to keep them quasi-namespaced:

As you’ve guessed, we’ve put this code in the project’s lib directory, under the name Strings.class.php, to make autoloading work for us. Now all we need to do to generate a slug is to call the Strings::CreateSlug() function. The example is purposefully verbose and inefficient, so I leave it up to the reader to apply the appropriate changes before actually using this code.

For example, the code above generates a slug for “A quick trip to the Bahamas” as follows: a-quick-trip-to-the-bahamas.

Let’s simplify our routing rule by just specifying:

/:post_slug

This simplifies the fetching of records by only searching using one field – the BlogPostPeer::POST_SLUG.

But alas! we are still at a crossroads – sooner or later someone will write a post with a preexisting title. In cases such as this, you could force the blogger to reconsider the title, but that’s just bad user experience. Instead, try checking for existing slugs first, then, if need be, append a unique identifier, such as an integer increment, or a date stamp. That way, you get the maximally readable URL without all the appended cruft that comes with a date-stamped URL or a similar scheme.

As always, this has only been a suggestion. Be creative with URLs, the more descriptive they are, the more SEO-friendly they are before you even start thinking about it!

7 Comments

You forgot about the archive browsing ability with those date-time URLs. Stripping the $day out of the URL gives me the archive of year 2007 and month 01, stripping out the month gives me the archive of year 2007. That’s much more SEO-friendly in my eyes.

I badly understand English, but in general I have understood this clause
, also I wish to tell that in my head that that has exchanged. Now I shall think on another. Actually I shall try it, and I hope to me it will help. And I consider that in some moments you are really right. But not on 100 %, excuse