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 ...
- visit
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 [];
}
}
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)
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?
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.
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.
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.
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
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.