Using Slugs/Permalinks Instead of IDs

Search engines like to see keyword phrases in the URL instead of a number for an ID. URL “slugs” can help describe your content better, which is a benefit to the visitors to your site, both human and crawl bot. When your indexed page is listed on a Google search results page, the snippet is displayed and skimmed by the reader. Also note that the keyword phrase you searched for will be highlighted, if displayed in the URL.

Luckily, you can get this up and running quickly with an extension already available for Yii, called yiibehaviorsluggable. All I had to do was add a new field for the slug into the database, drop the extension in its proper folder, and add the behavior to my model. The cool thing about this plugin, is that it can generate the slug for you when you update, based upon another field such as “title”. You may need to update the urlManager in your config. I have one of mine set to:

'product/<slug:[a-zA-Z0-9-]+>'=>'product/view'

This means my “product” pages would be accessed through a URL like:

  • www.mywebsite.com/product/slug-field-goes-here

Since the actionView function (in the controller) was using the ID, and the generated function loadModel, I created a second function loadModelSlug, and changed the view parameter and function to use it.

Removing /index.php/ from URLs

The default generated skeleton application that you are probably working from, if you didn’t start from scratch, starts most of the URLs with /index.php/. Now that’s ugly. Much like the way CodeIgniter’s index.php works (or WordPress even), an htaccess rule will rewrite the accessed pages to the index.php.

  1. Grab these htaccess rules from the offical guide and place them in the .htaccess in the root directory for your project. Read more about this change on that Yii guide page.
  2. You’ll also want to change these options in your config file:

Force Trailing Slashes to Avoid Duplicate Content

I noticed that pages could be accessed with or without the trailing slash. Since it’s best if the page is only accessible through one URL, to avoid duplicate content issues with Google, I looked for solutions on how to force the trailing slash. While you could do this with htaccess rewrites, it can also be done via a base Yii controller fairly easily.

Have all your controllers derive from your own custom base controller, that extends the default CController. If you’re using the Gii generated application, you’ll already have one called Controller.php located in /components/. Then, add the following code to it:

Get Rid of Ugly “Page” URLs in the Site Controller

When generating a skeleton application with Yii, it creates a SiteController which has an action for displaying pages. For example, a page would be “/site/page/?view=about”, when we really want something nice and short like “/about/”.

One way is to add individual rules for each page into your urlManager options. These are located in “/protected/config/main.php”.

That works, but what if you have a lot of pages? Instead of writing a rule for each one, I thought, what if I make it the last default rule past the generic controller/action rules? I tested out the following and it works. If there is no controller with the same name as the page, the SiteController page action will be the last rule Yii attempts to match. Here are my full urlManager settings for reference. See the last line:

Good luck in your development of an SEO-friendly Yii application. If you’re launching a new site, I highly recommend checking out this massive SEO guide from SEOMoz.

Comments on this Article

  1. Beginner says:

    Good tutorial for beginners like me

  2. Sugato says:

    THis is exactly what i needed! I am trying to create a blog in YII which will look basically like WordPress to search engines. Thanks for the useful post šŸ™‚

  3. Erik says:

    Thank You! After 2 hrs of trying to figure this out i implemented what i needed from here and got it working in like 2 minutes!

  4. Renate says:

    Thanks! Great!
    However I have a question:

    1) loadModelSlug: would not it be very slow if we use loadmodelSlug which loads model by Slug comparison instead of loadModel which loads model by ID which is much more faster?
    How to deal with this problem?
    Is not it better to have URL like noki-cell-phone-12 where 12 is ID?

    2) Second what is your recommendation if I have products by categories:
    is is better to have something like
    a) mypage.com/products/cell-phones/nokia-n12 (were products is Controller, “cell-phones” is product category and nokia-n12 is slug) or
    b) simple mypage.com/products/nokia-n12 or
    c) mayby something like mypage.com/products/cell-phone-nokia-n12 (were slug contains category)?
    d) any other suggestions?

  5. Elson James says:

    What happens if I have an action edit() on my product controller? I think that would conflict with your rule here:
    ‘product//’=>’product/view’,

    Any solutions on that?

    • Josh says:

      I think if you have your edit rule first, it should match that and not get to the product view rule using the slug. You also could change the URL format to something else, like /product/detail/SLUG/

  6. German says:

    Hi! Nice post… one question… how can I avoid all this urls to point the same place?

    http://www.yiiframework.com/site/index
    http://www.yiiframework.com/site/
    http://www.yiiframework.com/
    http://www.yiiframew....php/site/index
    http://www.yiiframew...index.php/site/
    http://www.yiiframework.com/index.php
    http://www.yiiframew...hp?r=site/index
    http://www.yiiframew...m/?r=site/index
    

    Thanks!

    • Josh Winn says:

      I would do a 301 redirect in the htaccess for /site/index. /site/ gives me a view not found, so that’d be particular to your app or controller. For the index.php ones with query strings, perhaps an htaccess rule as well; I don’t have an automatic way for that off-hand, as I just don’t generate those type of URLs to be indexed.

  7. platix says:

    Please fix the post at the end because it don’t show your reference urlManager settings.
    Thanks

  8. Petr says:

    Hello, nice article!

    How can I set 301 url for some entity, if it is accessed directly by id:

    site.ru/videos/1 –> site.ru/videos/my-super-video-name

    Sef-urls are stored in model and can be edited by admin of the site.
    In the view controller I search for a proper video by PK if sef part is numeric (videos/1) or by sef_url property if it is not (videos/my-super-video-name); therefore video can be accessed via two different urls.

    I need to redirect “numeric” urls to “sef-friendly” ones. At the moment I have a list of redirects in my .htaccess file, but is there a way to make it work automatically?

    • Josh says:

      I think you could do the redirect via PHP in the controller. Check the URL segment(s) for the numeric version, then do a header('Location:NEW_URL_HERE'); to redirect. Only works before anything is displayed on the page.

  9. Nilesh says:

    http://www.crustmedia.com/search/aaj-tak
    I want to hide the function name search from url . The search function is in site controller.
    I have already hide controller name but i also want to hide search function name.
    The URL should look like this : www.crustmedia.com/aaj-tak

    • Josh says:

      Try putting your search controller as the last entry in your urlManager. That way anything that uses another controller that comes before it will still work, and then the search will be the default last check for any URL. You’d probably want to make sure your search results aren’t indexable by Google though since any gibberish would be a page (robots noindex meta).

Comments are closed.