Yii2 :: SEO essentials

1. Enable pretty URLs

Sometimes users want to share your site URLs via social networks.  For example, by default your about page URL looks like http://mkchowdhury.com/index.php?r=site%2Fabout.  Let’s imagine this link on Facebook page. Do you want to click on it? Most of users have no idea what is index.php and what is %2.  They trust such link less, so will click less on it. Thus web site owner would lose traffic.

URLs such as the following is better: http://mkchowdhury.com/about. Every user can understand that it is a clear way to get to about page.

Let’s enable pretty URLs for our Yii project.

1.1  Apache Web server configuration

If you’re using Apache you need an extra step.  Inside your .htaccess file in your webroot directory or inside location section of your main Apache config add the following lines:

RewriteEngine on
# If a directory or a file exists, use it directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# Otherwise forward it to index.php
RewriteRule . index.php

1.2  URL  manager configuration

Configure urlManager component in your Yii config file:

‘components’  =>  [
//…
‘urlManager’  =>[
‘class’  =>  ‘yii\web\UrlManager’,
‘showScriptName’  => false, // Hide  index.php
‘enablePrettyUrl’  => true, // Use  pretty  URLs
‘rules’  =>  [
],
],
//
],

Remove site parameter from  URL

After previous steps you will get http://mkchowdhury.com/site/about link.  site parameter tells nothing helpful to your users.  So remove it by additional urlManager rule:

‘rules’  =>  [
‘<alias:\w+>’  =>  ‘site/<alias>’,
],

As a result your URL will looks like http://mkchowdhury.com/about.

2.   Pagination pretty URLs

For example we can render our site content by GridView. If there are a lot of content rows we use pagination. And it is necessary to provide GET request for every pagination page. Thus search crawlers can index our content. We also need our URLs to be pretty. Let’s do it.

2.1. Initial state

For example we have a page with such URL http://mkchowdhury.com/ schools/ schoolTitle.   Parameter schoolTitle  is a title  parameter for our request.

In the application config file we have:

‘components’  =>  [
//…
‘urlManager’  =>[
//….
‘rules’  =>  [
‘schools/<title:\w+>’ => ‘site/schools’,

],
],
//
],

We  decided  to  add  a GridView  on the  page ‘http://mkchowdhury.com/schools/schoolTitle’. Pagination pretty URLs.

When we click on pagination link our URL is transformed to ‘http://example. com/schools/schoo1Title?page=2’.

We want our pagination link looks like ‘http://mkchowdhury.com/schools/schoolTitle/2’.
Let’s add new urlManager rule  higher than  existed  rule. Here it is:

‘components’  =>  [
//…
‘urlManager’  =>[
//….
‘rules’  =>  [
‘schools/<title:\w+>/<page:\d+>’ => ‘site/schools’, // new rule
‘schools/<title:\w+>’ => ‘site/schools’,

],
],
//
],

3.  Adding SEO tags

Organic search is an excellent traffic source.  In order to get it you have to make a lot of small steps to improve your project.

One of such steps is to provide different meta tags for different pages. It will improve your site organic search appearance and may result in better ranking.

Let’s review how to add SEO-related metadata to your pages.

3.1. Title

It is very simple to set title. Inside controller action:
\Yii::Sapp->view->title  =  ‘title  set  inside  controller’;

Inside a view:
$this->title  =  ‘Title  from view’;

Note:  Setting $this->title in layout will override value which is set for concrete view so don’t do it.

It’s a good idea to have default title so inside layout you can have something like the following:
$this->title  = $this->title  ? $this->title  :  ‘default  title’;

3.2.  Description and Keywords

There are no dedicated view parameters for keywords  or description.  Since these are meta tags and you should set them by registerMetaTag()  method.

Inside controller action:

\Yii::$app->view->registerMetaTag([
‘name’=>’description’,
‘content’=>’Descriptionsetinsidecontroller’,
]);
\Yii::$app->view->registerMetaTag([
‘name’=>’keywords’,
‘content’=>’Keywordssetinsidecontroller’,
]);

Inside a view:

$this->registerMetaTag([
‘name’=>’description’,
‘content’=>’Descriptionsetinsideview’,
]);
$this->registerMetaTag([
‘name’=>’keywords’,
‘content’=>’Keywordssetinsideview’,
]);

All registered meta tags will be rendered inside layout in place of $this->head() call.

Note that when the same tag is registered twice it’s rendered twice. For example, description meta tag that is registered both in layout and a view is rendered twice. Usually it’s not good for SEO. In order to avoid it you can specify key as the second argument of registerMetaTag:

$this->registerMetaTag([
‘name’=>’description’,
‘content’=>’Description1’,
],’description’);

$this->registerMetaTag([
‘name’=>’description’,
‘content’=>’Description2’,
],’description’);

In this case later second call will overwrite first call and description will be set to “Description 2”.

4.   Canonical URLs

Because of many reasons the same or nearly the same page content often is accessible via multiple URLs.  There are valid cases for it such as viewing an article within a category and not so valid ones.  For end user it doesn’t really matter much but still it could be a problem because of search engines because either you might get wrong URLs preferred or, in the worst case, you might get penalized.

One way to solve it is to mark one of URLs as a primary or, as it called, canonical, one you may use <link rel=”canonical” tag in the page head.

Note: In the above we assume that pretty URLs are enabled. Let’s imagine we have two pages with similar or nearly similar content:

  • http://mkchowdhury.com/item1
  • http://mkchowdhury.com/item2

Our goal is to mark first one as canonical.  Another one would be still accessible to end user.  The process of adding SEO meta-tags is described in “adding SEO tags” recipe. Adding <link rel=”canonical” is very similar. In order to do it from controller action you may use the following code:

\Yii::$app->view->registerLinkTag([‘rel’=>’canonical’,’href’=> Url::to([‘item1’],true)]);

In order to achieve the same from inside the view do it as follows:

$this->registerLinkTag([‘rel’=>’canonical’,’href’=> Url::to([‘item1’],true)]);

Note:  It is necessary to use absolute paths instead of relative ones.

As an alternative to Url :: to() you can use Url :: canonical() such as

$this->registerLinkTag([’rel’=>’canonical’,’href’=> Url::canonical()]);

The line above could be added to layout. Url :: canonical() generates the tag based on current controller route and action parameters ( the ones present in the method signature).

5.  Using redirects

301

Let’s imagine we had a page http://mkchowdhury.com/item2 but then permanently moved content to http://mkchowdhury.com/item1. There is a good chance that some users (or search crawlers) have already saved http://mkchowdhury.com/item2 via bookmarks, database, web site article, etc. Because of that we can’t just remove http://mkchowdhury.com/item2. In this case use 301 redirect.

class MyController extends Controller
{
public function beforeAction($action)
{
if(in_array($action->id, [‘item2’])) {
Yii::$app->response->redirect(Url::to([‘item1’]), 301);
Yii::$app->end();
}
return parent::beforeAction($action);
}

For further convenience you can determine an array. So if you need to redirect another URL then add new key>value pair:

class MyController extends Controller
{
public function beforeAction($action)
{
$toRedir = [
‘item2’=>’item1’,
‘item3’=>’item1’,
];
if(isset($toRedir[$action->id])) {
Yii::$app->response->redirect(Url::to([$toRedir[$action->id]]),
301);
Yii::$app->end();
}
return parent::beforeAction($action);
}

6. Using slugs

Even when pretty URLs are enabled, these often aren’t looking too friendly:
http://mkchowdhury.com/post/42

Using Yii it doesn’t take much time to make URLs look like the following:
http://mkchowdhury.com/post/hello-world

6.1. Preparations

Set up database to use with Yii, create the following table:

post
===
id, title, content

Generate Post model and CRUD for it using Gii.

6.2. How to do it

Add slug field to post table that holds our posts. Then add sluggable behavior to the model:

<?php
use yii\behaviors\SluggableBehavior;
//…
class Post extends ActiveRecord
{
//…
public function behaviors()
{
return [
[
‘class’=> SluggableBehavior::className(),
‘attribute’=>’title’,
],
];
}
//…
}

Now when post is created slug in database will be automatically filled. We need to adjust controller. Add the following method:

protected function findModelBySlug($slug)
{
if(($model = Post::findOne([‘slug’=> $slug])) !== null) {
return $model;
}else{
throw new NotFoundHttpException();
}
}

Now adjust view action:

public function actionView($slug)
{
return $this->render(‘view’, [
‘model’=> $this->findModelBySlug($slug),
]);
}

Now in order to create a link to the post you have to pass slug into it like the following:

echo Url::to([‘post/view’,’slug’=> $post->slug]);

6.3. Handling title changes

There are be multiple strategies to deal with situation when title is changed. One of these is to include ID in the title and use it to find a post.

http://mkchowdhury.com/post/42/hello-world

 7. Handling trailing slash in URLs

By default Yii handles URLs without trailing slash and gives out 404 for URLs with it. It is a good idea to choose either using or not using slash but handling both and doing 301 redirect from one variant to another.

For example,

/hello/world – 200
/hello/world/ – 301 redirect to /hello/world

7.1. Using UrlNormalizer

Since Yii 2.0.10 there’s UrlNormalizer class you can use to deal with slash and no-slash URLs in a very convenient way.  Check out the “URL normalization  section” in the official guide for details.

7.2. Redirecting via web server config

Besides PHP, there’s a way to achieve redirection using web server.

7.3. Redirecting via nginx

Redirecting to no-slash URLs.

location / {
rewrite ^(.*)/$ $1 permanent;
try_files $uri $uri/ /index.php?$args;
}

Redirecting to slash URLs.

location / {
rewrite ^(.*[^/])$ $1/ permanent;
try_files $uri $uri/ /index.php?$args;
}

7.4. Redirecting via Apache

Redirecting to no-slash URLs.
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)/$ /$1 [L,R=301]
Redirecting to slash URLs.
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*[^/])$ /$1/ [L,R=301]