SEO Friendly URLs in Yii2

Author Alexander
This tutorial provides some insights into Apache configuration and Yii2 configuration in order to achieve user friendly and search engine friendly URLs for your web app.

Basic Template

Yii2 official documentation has a page about Shared Hosting configuration that provides an example of how you can configure Apache for SEO friendly URLs using an .htaccess file.
Options +FollowSymLinks
IndexIgnore */*
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
After that you need to configure urlManager in your config/web.php file.
$config = [
 //...
 'components' => [
 'urlManager' => [
 'class' => 'yii\web\UrlManager',
 'showScriptName' => false,
 'enablePrettyUrl' => true,
 ],
 //...
If your application is located in the root folder the routes will look something like this: http://yourdomain.com/web/site/index, otherwise if your Yii2 app is in a subfolder they might look like this http://yourdomain.com/app/web/site/index.

Hide the web folder

You can get rid of the web part of the URL by moving all the files from the web folder to your applications root folder and changing the paths in index.php, but this method will mix the contents of the /assets/ folder and the /web/assets/ folder. This is why this solution is better suited for the advanced application template, unless you are willing to move all of your code along with the vendors folder into a separate folder like Yii 1.x did.
A better way to solve this is by adding an additional .htaccess file to the root of your app.
Options -Indexes
RewriteEngine on
RewriteRule ^(.*)$ web/$1 [L]
Note that this will only work if you have the aforementioned .htaccess file in the web directory. Otherwise the request will get caught in an internal redirect loop.
There are many websites that show similar configurations that are more complex. Since we are redirecting all requests to the web folder there is no need to deny access to files that start with a dot, JSON files and such. The Basic Template doesn’t have any of those files in the web folder. For the advanced template you can deny access to dotfiles by adding these lines.

Order allow,deny
Deny from all
Now we have to tell Yii that our base URL has changed. We do this by configuring baseUrl for urlManager and the request component in config/web.php.
$config = [
 //...
 'components' => [
 'urlManager' => [
 'class' => 'yii\web\UrlManager',
 'showScriptName' => false,
 'enablePrettyUrl' => true,
 'baseUrl' => '/',
 ],
 'request' => [
 'baseUrl' => '',
 //...
 ],
 //...
If your app is in a subfolder put the name of the subfolder into the baseUrl parameter ‘baseUrl’ => ‘/app’,.

Advanced Template

We can configure the advanced template similarly to the basic template. The .htaccess files in the web folders will be the same. But the .htaccess file for the root folder will be different to allow requests to the backend app.
Options -Indexes
RewriteEngine on
#if the request is not to the backend, route it to /frontend/web
RewriteCond %{REQUEST_URI} !^/backend
RewriteRule ^(.*)$ /frontend/web/$1 [L]
#otherwise route the request to the backend
RewriteRule ^backend/(.*)$ backend/web/$1 [L]
These rules don’t cause a redirect loop because the request is further rewritten by the rules in the .htaccess file of the web directory.
Don’t forget to configure the baseUrl for the request component and the urlManager.

Links from backend to frontend

The official documentation suggests that in order to make links from the backend app to the frontend app and from the frontend app to the backend app we have to configure an additional urlManagerBackend component for the frontend app.
'urlManager' => [
 'class' => 'yii\web\UrlManager',
 'baseUrl' => '',
 'enablePrettyUrl' => true,
 'showScriptName' => false,
],
'urlManagerBackend' => [
 'class' => 'yii\web\UrlManager',
 'baseUrl' => '/backend',
 'enablePrettyUrl' => true,
 'showScriptName' => false,
],
And an addition urlManagerFrontend for the backend app.
'urlManager' => [
 'class' => 'yii\web\UrlManager',
 'baseUrl' => '/backend',
 'enablePrettyUrl' => true,
 'showScriptName' => false,
],
'urlManagerFrontend' => [
 'class' => 'yii\web\UrlManager',
 'baseUrl' => '',
 'enablePrettyUrl' => true,
 'showScriptName' => false,
],
Then you can use them like so: $frontendUrl = \Yii::$app->urlManagerFrontend->createUrl([‘site/index’]);
This is great when you don’t have any URL rules, otherwise not only will you have to duplicate all the rules, but you will also have to change them in two places. To avoid this you can store the configuration for urlManagers in separate files and load the with the require function.
'urlManager' => require('urlmanager.php'),
'urlManagerBackend' => require('../../backend/config/url-manager.php'),
For the backend:
'urlManager' => require('urlmanager.php'),
'urlManagerFrontend' => require('../../frontend/config/url-manager.php'),
And this is how you can keep the configuration inside your url-manager.php files.
 'yii\web\UrlManager',
 'showScriptName' => false,
 'enablePrettyUrl' => true,
];

Trailing slash

If you’ve ever used nibbler you might have gotten a notification about a trailing slash problem.

You can fix this by adding these two lines to your /web/.htaccess file(s) right after RewriteEngine on:
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)/$ /$1 [L,R=301]

Configuring Apache Directly

.htaccess files are convenient because you can bundle them together with your application and because you don’t have to  restart Apache after changing something inside an .htaccess file in order for the changes to take effect. But you also have to pay with performance because Apache has to scan the .htaccess files on every request. To avoid that you can configure Apache from httpd.conf.
Here’s an example configuration I used for a VirtualHost on a windows machine.

 ServerAdmin webmaster@mytestapp
 DocumentRoot "C:\wamp\www\mytestapp\frontend\web"
 ServerName mytestapp
 ErrorLog "logs/mytestapp-error.log"
 CustomLog "logs/mytestapp-access.log" common
 Alias /admin "C:\wamp\www\mytestapp\backend\web"
 
 RewriteEngine on
 RewriteCond %{REQUEST_FILENAME} !-f
 RewriteCond %{REQUEST_FILENAME} !-d
 RewriteRule . index.php
 AllowOverride None
 Order allow,deny
 Allow from all
 
 
 RewriteEngine on
 RewriteCond %{REQUEST_FILENAME} !-f
 RewriteCond %{REQUEST_FILENAME} !-d
 RewriteRule . index.php
 AllowOverride None
 Order allow,deny
 Allow from all
 
Note that in this configuration the backend app is available through the /admin route.

The Rules

Now that we’ve got the configuration out of the way we can start improving our URLs.
'urlManager' => [
 'class' => 'yii\web\UrlManager',
 'baseUrl' => '/',
 'enablePrettyUrl' => true,
 'showScriptName' => false,
 'enableStrictParsing' => true,
 'rules' => [
 '/' => 'site/index',
 'about' => 'site/about',
 'contact' => 'site/contact',
 'login' => 'site/login',
 'logout' => 'site/logout',
 'captcha' => 'site/captcha',
 'signup' => 'site/signup',
 'request-password-reset' => 'site/request-password-reset',
 'reset-password' => 'site/reset-password',
 ],
],
‘enableStrictParsing’ => true, tells Yii2 to allow only the routes that are listed in the rules. It’s up to you to decide whether you want it or not.
A tutorial for advanced routing with urlManager is coming soon.

Comments