Magento 2 remove trailing slashes from urls and set up redirects

This is a common task for any Magento 2 store that is working towards optimizing search result ranking. However a lot of the times it is not entirely obvious why removing slashes would help. Or is it ok to append the slashes to all urls? Lets dive in and explore the nature of the problem.

To slash or not to slash

There is an old Google article from ~2010 which summarizes the main ideas. Although the article has been around for quite some time it is still true. Unfortunately a lot of confusion is still floating around the web.

To understand the nature of the problem we need to look back a few decades and remember how the traditional websites were built. In those the a trailing slash was used to indicate a directory on the server while the urls without a trailing slash pointed to a specific file. However those days are long gone - still Google considers these urls to be different content for this exact reason. Nowadays websites will usually lead to the same content with or without slash - and since Google considers each url to be a separate entity a few problems arise:

  • urls can be marked as duplicate content
  • the search value for both urls are split in two

Both of these are major issues that can affect your overall search ranking.

What to do

Now that we understand the problem let's look at how to fix it. In theory all we have to do is make sure only one version of the page is available and used on the page. It does not matter if you use trailing slashes or not however it is suggested to keep it consistent across the site. The consistency will likely not affect you seo scores however it will create a better user experience.

If your site has a directory structure then it may be more convenient to use trailing slashes everywhere and redirect the non-trailing slash versions to them. However there are no limitation here - it is a matter of preference.

Magento 2

In this article we will take a look specifically what we can do to automatically remove the trailing slashes from the generated urls and how to redirect urls to a single version.

The first step is to generate the urls without the trailing slash. To accomplish this we will create a few plugins for methods that are responsible for outputting and fetching the urls. Lets start by defining the plugins in di.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Framework\Url">
        <plugin name="techflarestudio_slashes_magento_framework_url" type="Techflarestudio\Slashes\Plugin\Magento\Framework\Url"/>
    </type>
    <type name="Magento\Framework\UrlInterface">
        <plugin name="techflarestudio_slashes_magento_framework_urlinterface" type="Techflarestudio\Slashes\Plugin\Magento\Framework\UrlInterface"/>
    </type>
    <type name="Magento\Framework\View\Element\Html\Link\Current">
        <plugin name="techflarestudio_slashes_magento_framework_view_element_html_link_current" type="Techflarestudio\Slashes\Plugin\Magento\Framework\View\Element\Html\Link\Current"/>
    </type>
</config>
app/code/Techflarestudio/Slashes/etc/di.xml

The first plugin is extending the Magento\Framework\Url and we will use it to trim the slash from the function getRouteUrl.

<?php

namespace Techflarestudio\Slashes\Plugin\Magento\Framework;

use Magento\Framework\Url as UrlFramework;

/**
 * Class Url
 * @package Techflarestudio\Slashes\Plugin\Magento\Framework
 */
class Url
{
    /**
     * @param UrlFramework $subject
     * @param $result
     * @return string
     */
    public function afterGetRouteUrl(
        UrlFramework $subject,
        $result
    ) {
        if (empty($result) || !is_string($result)) {
            return $result;
        }

        return rtrim($result, '/');
    }
}
app/code/Techflarestudio/Slashes/Plugin/Magento/Framework/Url.php

The second plugin will also hook into a framework class Magento\Framework\UrlInterface and we will use it to trim the slash from the default getUrl method.

<?php

namespace Techflarestudio\Slashes\Plugin\Magento\Framework;

use Magento\Framework\UrlInterface as UrlInterfaceFramework;

/**
 * Class UrlInterface
 * @package Techflarestudio\Slashes\Plugin\Magento\Framework
 */
class UrlInterface
{
    /**
     * Remove trailing slash for getUrl
     *
     * @param UrlInterfaceFramework $subject
     * @param string $result
     * @return string
     */
    public function afterGetUrl(
        UrlInterfaceFramework $subject,
        $result
    ) {
        if (empty($result) || !is_string($result)) {
            return $result;
        }

        return rtrim($result, '/');
    }
}
app/code/Techflarestudio/Slashes/Plugin/Magento/Framework/UrlInterface.php

The final method that we will hook into will also be a framework class Magento\Framework\View\Element\Html\Link\Current.

<?php

namespace Techflarestudio\Slashes\Plugin\Magento\Framework\View\Element\Html\Link;

/**
 * Class Current
 * @package Techflarestudio\Slashes\Plugin\Magento\Framework\View\Element\Html\Link
 */
class Current
{
    /**
     * Remove trailing slashes from hrefs
     *
     * @param $subject
     * @param $result
     * @return string
     */
    public function afterGetHref($subject, $result)
    {
        if (empty($result) || !is_string($result)) {
            return $result;
        }

        return rtrim($result, '/');
    }
}
app/code/Techflarestudio/Slashes/Plugin/Magento/Framework/View/Element/Html/Link/Current.php

After creating these 3 plugins the urls in the Magento 2 frontend should be generated without trailing slashes. Make sure to enable the module, clear the cache, generated files and test the result.

For some Magento version it is possible that you will run into the following error:

Notice: Undefined index: path in /var/www/magento2/public/vendor/magento/module-store/Model/Store.php on line 1240

You can easily work around this by creating a patch and rewriting the following method:

Model/Store.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Model/Store.php b/Model/Store.php
index dac4fcb..4204a74 100644
--- a/Model/Store.php
+++ b/Model/Store.php
@@ -1249,7 +1249,7 @@ class Store extends AbstractExtensibleModel implements
             . '://'
             . $storeParsedUrl['host']
             . (isset($storeParsedUrl['port']) ? ':' . $storeParsedUrl['port'] : '')
-            . $storeParsedUrl['path']
+            . (isset($storeParsedUrl['path']) ? $storeParsedUrl['path'] : '')
             . $requestStringPath
             . ($currentUrlQueryParams ? '?' . http_build_query($currentUrlQueryParams) : '');
app/code/Techflarestudio/Slashes/Plugin/Magento/Framework/View/Element/Html/Link/Current.php

All we are doing here is making sure we verify that the path is set before accessing it. This is not a problem for all Magento versions so apply only if required. Suggesting to either rewrite the class using a preference or creating a composer patch as shown above.

Now that all of the urls are generated and rendered without the trailing slash we need to perform the second step and make sure that the urls with a trailing slash are redirected to the non-trailing slash version. This is usually easiest to handle on the server level. The configuration adjustments depend on the software that is used. We will show an example for nginx however it should be possible to apply the same idea to any webserver.

if ($uri !~ /exclude|) {
    rewrite ^/(.*)/$ /$1 permanent;
}
sites-available/website

The above directive allows you to define paths (admin, custom routes) you do not want to rewrite and rewrite everything else to a version without a trailing slash. We are using a simple regex to match the url without a trailing slash and setting a permanent redirect to it.

Once you have implemented this you can attempt to visit your site with a trailing slash and you should be redirected to the version without a trailing slash. Such global adjustments can cause unexpected issues for 3rd party code so make sure to thoroughly test the functionality before applying this to production.

That is it for today. We have explored why it is important to consider trailing slashes in urls and what are our options for working around this issue.