DEV Community

Chris Shennan
Chris Shennan

Posted on • Edited on • Originally published at chrisshennan.com

Decorate the Symfony router to add a trailing slash to all URLs

I recently noticed an issue between the links that Symfony generated for Password Angel and the actual links that are in use. When Symfony builds the URL there are no trailing slashes i.e. /terms, however, as Password Angel is hosted in an S3 bucket as a static site a trailing slash is part of the live URL i.e. /terms/. This causes 2 problems:-

  • Unnecessary redirections - All links in the page will refer to the link version without the trailing slash and then the user will need to be redirected to the version with the trailing slash.
  • The canonical URLs are invalid - As I'm using Symfony to generate the canonical URL for each page, it generated the link version without the trailing slash. This may cause SEO issues as search engines will

    • visit /terms
    • be redirected to /terms/
    • be informed the original page is at /terms
    • ... go to step 1 - infinite loop ...

Solution - Decorate the Symfony Router

To resolve this I created a decorator for the Symfony default router and have overridden the generate method to add a slash to the end of the URL. It also checks for the presence of ? which would indicate there are query string parameters and in this situation, I am inserting the / before the ? as we want /terms/?utm_campaign=... and not /terms?utm_campaign=.../.

<?php

declare(strict_types=1);

namespace App\Service;

use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Router;
use Symfony\Component\Routing\RouterInterface;

#[AsDecorator('router.default')]
class TrailingSlashUrlGenerator implements RouterInterface, WarmableInterface
{
    public function __construct(
        private readonly Router $urlGenerator,
    ) {}

    public function generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH): string
    {
        // Original URL
        $url = $this->urlGenerator->generate($name, $parameters, $referenceType);

        // Add the slash before any query string parameters
        $pos = strpos($url, '?');
        if ($pos !== false) {
            $parts = explode('?', $url, 2);
            if (str_ends_with($parts[0], '/') === false) {
                $parts[0] .= '/';
                return implode('?', $parts);
            }
        }

        // Add the slash at the end of the URL
        if (str_ends_with($url, '/') === false) {
            $url .= '/';
        }

        return $url;
    }

    public function match(string $pathinfo): array
    {
        return $this->urlGenerator->match($pathinfo);
    }

    public function getRouteCollection(): RouteCollection
    {
        return $this->urlGenerator->getRouteCollection();
    }

    public function setContext(RequestContext $context): void
    {
        $this->urlGenerator->setContext($context);
    }

    public function getContext(): RequestContext
    {
        return $this->urlGenerator->getContext();
    }

    public function warmUp(string $cacheDir, ?string $buildDir = null): array
    {
        return [];
    }
}
Enter fullscreen mode Exit fullscreen mode

Note: To host Password Angel as a static site on S3, I have written a Symfony command to generate static versions of all the pages (all 4 of them) and these are uploaded to S3. Let me know if you're interested and I'll post up how the Symfony command works.


Originally published at https://chrisshennan.com/blog/decorate-the-symfony-router-to-add-a-trailing-slash-to-all-urls

Top comments (4)

Collapse
 
xwero profile image
david duymelinck

If there is no solution for removing the trailing slashes with S3 static hosting; I suggest you use another hosting platform.

Why would you want to let you hosting platform dicate your url structure?

Collapse
 
chrisshennan profile image
Chris Shennan

Good question. I can think of a couple of reasons why using another host, or changing the trailing slash behaviour at the provider may not be viable. 

  • It's not your choice. - Most of us don't get a choice of where our application is hosted. Commercial agreements are already in place, often spanning multiple years so we have to work with what's available.
  • Red tape - A change in provider, or even a trailing slash configuration at the infrastructure level might require a change request to be raised with a separate DevOps teams. For something like changing the trailing slash policy, this will likely be at the bottom of their list. Managing this within your application can mean it's resolved in hours rather than days or weeks.
  • Complexity/Flexibility - This isn't just an AWS issue. Many static site host solutions have trailing slashes at the end of their URLs out of the box, and there are workarounds to remove them (including for AWS). Developers aren't always aware of the infrastructure that their application runs on (although a basic understanding of it is beneficial). Keeping the application routing within the application rather than a separate DevOps function, which is possibly not available for developers to see, reduces cognitive complexity and offers greater flexibility in the future (no request needs to be raised with DevOps for a change).
  • Convenience (and I fall into this) - I could use another host but Password Angel is one small project amongst several others using several AWS features. While I could separate it, having all my projects in one place greatly outweighs any benefit that would come from splitting them into multiple providers to overcome a trailing slash minor inconvenience.

Ultimately having a trailing slash or not isn't a big deal - search engines can handle both just fine and static site providers tend to automatically redirect non-trailing slash URLs to ones with trailing slashes i.e. passwordangel.co/terms will redirect to passwordangel.co/terms/ (so no broken links!) - what is important is consistency.

However the pros and cons of which provider to use and whether to use a trailing slash or not are beyond the scope of this article.  Having decided you want a trailing slash at the end of your URLs, this article is aimed at showing you how to achieve that with minimal effort and fuss.

Collapse
 
xwero profile image
david duymelinck

I understand your post is about how to solve a problem that other people will face.

I still think a hosting service that requires you to add a trailing slash is bad default.
The technological view is that if you have a trailing slash is it an overview of a folder instead of a single entity.
If you look at it from an SEO view, people are not going to search for anything with a trailing slash.

I think if you take convenience over a better way, that is a slippery slope. How much of making users doing not intuitive things are you going to allow, before you are going to intervene?
If you have no other option, your hands are tied.

Thread Thread
 
chrisshennan profile image
Chris Shennan

Some static site generators (as is the case with passwordangel.co) produce a structure where it is powered in directories - each page, homepage, terms, and privacy policy - is a directory with an index.html file inside it, so from a technical standpoint the trailing slash is correct.

There could equally be an argument made here that if the static site generator forces that directory structure, and therefore the trailing slash, then we should look to change to another site generator, why would we allow it to dictate our URL structure? (Full disclosure - my static site generator is homegrown).

There are a variety of solutions that could be considered before changing providers

  • Change the static site structure to produce a structure like [page].html files rather than [page]/index.html
  • Go the traditional route and have an EC2 box running nginx serve the static site.
  • A lambda function to remove the trailing slash.

In terms of SEO, "people are not going to search for anything with a trailing slash." - correct, but they aren't going to search for anything without it either - it's the keywords in the URL, in the titles, the body content, brand names, long tail keyword phrases etc - none of which are impacted by a trailing slash or not. As long as the URLs are consistent - and the redirect from non-trailing slash -> trailing slashes is working, then it makes little difference from an SEO perspective if you have a trailing slash or not.

People are not going to manually type your link into their articles, they will copy-and-paste from the address bar, or use share buttons on the page, so the trailing slash, or no trailing slash, is going to be copied and pasted into their articles.

I am playing a little devil's advocate here, I do agree that what we build should be the most intuitive and useful for your users, and we should always strive for that in everything we do, but I'm also a believer in the 80/20 and I believe there are likely to be much bigger wins to be made elsewhere. In the end, it always boils down to an element of compromise and balance and how willing you are to do both.

P.S. This conversation has been great and has given me some pause for thought. I'm going to look into the "Change the static site structure to produce a structure like [page].html files rather than [page]/index.html" alternative to see if it gives me any benefit over my current static site generation approach as it might be a minimal approach which doesn't require rewiring the whole application.