Apache & Nginx : servir des fichiers sans extension
Les pretty urls comme on les appelles communément, consistent à faire abstraction de l’extension des fichiers dans les urls. Ainsi, https://buzut.net/a-propos.html
se transforme en https://buzut.net/a-propos
. C’est plus court, c’est plus clean, c’est plus SEO, bref, c’est plus mieux. Voyons comment faire cela avec les serveurs les plus courants : Apache2 et Nginx.
Vous noterez que certains sites servent toutes leurs urls avec un slash de fin. Il s’agit souvent d’une volonté esthétique. Cela dit, on distingue en général les répertoires, qui se terminent par un slash, des fichiers, qui ne se terminent pas par un slash.
Ainsi, https://buzut.net/test/
fait référence au répertoire nommé “test”, tandis que https://buzut.net/test
fait référence à un fichier “test” placé à la racine du site.
Bien que ce blog ne respecte pas cette convention (CMS inside), nous allons tout de même nous conformer à cette dernière dans la suite de cet article.
Le principe
Commençons par un micro cahier des charges. Il y a trois critères à respecter :
- Les urls sont accessibles sans leur extension,
- SEO oblige, il faut rediriger la version avec extension vers la version sans (sinon on se retrouve avec toutes les pages en double),
- concernant l’index, on ne redirige pas vers la version sans extension, mais vers la racine (
https://buzut.net/index
, ça fait bizarre…).
Petit détail, lorsqu’on détecte index.html
, on ne redirige pas comme un bourrin sur la racine. En effet, de nombreux sites possèdent des index à d’autres niveaux. Pour la version localisée par exemple, il serait mal venu d’être redirigé vers une page dans une autre langue…
Bref, assez discuté, commençons !
Apache2
RewriteEngine on
RewriteBase /
RewriteCond %{REQUEST_URI} ^(.*)index\.html$ [NC]
RewriteRule ^ %1 [R=301,L]
RewriteCond %{THE_REQUEST} ^[A-Z]{3,}\s([^.]+)\.html [NC]
RewriteRule ^ %1 [R=301,L]
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)/$ $1 [R=301,L]
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}\.html -f
RewriteRule ^(.*)$ $1.html
Nous avons ici cinq blocs distincts. Expliquons rapidement le rôle de chacun. On commence par activer la redirection d’url – il faut tout de même que le mod_rewrite
soit actif – puis on définit ensuite la base pour les chemins relatifs.
Ensuite, pour toute url contenant index.html
, on capture ce qui est avant index.html
et on le réécrit sans le fichier. Donc http://monsite.fr/en/index.html
sera bien redirigé vers http://monsite.fr/en/
et non http://monsite.fr/
.
Le bloc suivant capture toutes les url contenant l’extension .html
et réécrit l’url sans cette dernière.
Notez bien que dans les deux paragraphes précédents, lorsque je parle de réécriture, il s’agit bien ici de redirections de type 301 (R=301
).
L’avant dernier bloc permet d’enlever le slash de fin d’url s’il ne s’agit pas d’un répertoire. Cela planterait Apache à cause d’une bloucle de redirection. Il est très improbable que nous ayons des noms de fichier du type fichier/.html
.
Enfin, le dernier bloc fait en sorte qu’une requête qui ne mène théoriquement à rien – chez A Partners, dans https://a-partners.eu/team
, contact
n’existe pas, c’est bien contact.html
qui existe – serve le bon contenu.
Pour se faire, on vérifie que l’url ne fait pas référence à un répertoire et que si on la suffixe par .html
, cela correspond à un fichier. Si ces deux conditions sont remplies, alors on sert le fichier en question.
Si vous souhaitez creusez tout cela, je vous laisse entre les mains de la doc Apache.
Nginx
Nous allons faire exactement la même chose avec Nginx. De nombreux exemples trainent sur le web, dont un certain nombre ne fonctionnent pas (ou plus avec les versions récentes de Nginx). En outre, on lit souvent qu’il ne faut pas utiliser les if
. C’est vrai dans de nombreux cas, mais la doc de Nginx dans son if is evil, précise bien que l’usage que nous allons en faire est 100% safe.
Il est possible de se passer des if
en utilisant plusieurs bloc location
, mais il n’est pas possible avec cette technique de différencier index.html
des autres fichiers.
Nous allons donc utiliser des if
. C’est en plus bien plus concis et plus court. Cela dit, je suis ouvert à toute remarque ou suggestion dans les commentaires.
location / {
if ($request_uri ~ ^/(.*)index(\.html)?$) { return 301 /$1; }
if ($request_uri ~ ^/(.*)\.html$) { return 301 /$1; }
try_files $uri $uri.html $uri/ =404;
}
Dans notre bloc s’appliquant à la racine, nous testons si l’url contient index.html
ou index
tout court, si c’est le cas, on capture ce qui précède et on redirige en enlevant juste la partie superflue ; sinon, on teste si l’url contient .html
, si oui on redirige en enlevant l’extension.
Enfin, dans tous les autres cas, on utilise try_files
pour servir le fichier/répertoire. Dans l’ordre :
- s’il s’agit d’un fichier, on le sert,
- si l’uri correspond à un fichier une fois ajouté le suffixe
.html
, on le sert, - s’il s’agit d’un répertoire, on le sert,
- enfin, si ce n’est rien de tout cela, on retourne une erreur 404.
Sachez que, de par la comportement des directives location
(s’il y a plusieurs matchs, le plus long est pris en compte), des directives générales comme celle-ci pour gérer l’expiration des contenus, empêcheront votre bloc de s’exécuter.
La doc est toujours disponible si vous voulez creuser un peu plus le fonctionnement des blocs location
ou des try_files
.
Et voilà, que vous utilisez Nginx ou Apache, vous n’avez maintenant plus aucune excuse pour ne pas avoir des url bien proprettes ! N’hésitez pas à utiliser les commentaires si vous avez des remarques et/ou une autre approche.
Commentaires
Rejoignez la discussion !