Après une refonte, les anciennes adresses du type /produits?marque=ancienne ou /fiche.php?id=42 sont encore dans Google et dans les favoris des clients. Une RewriteRule seule ne les voit pas : dans Apache, le motif d'une règle porte sur le chemin, jamais sur ce qui suit le ?. Il faut une condition RewriteCond %{QUERY_STRING}, puis décider ce que devient la chaîne de paramètres : remplacée, supprimée ou conservée. Chaque règle de cette note a été testée avec Apache et Nginx dans Docker, puis vérifiée avec curl.
Pourquoi une RewriteRule seule ne voit pas les paramètres ?
La documentation de mod_rewrite est explicite : le motif est comparé au chemin de l'URL, "before the query string". Pour tester les paramètres, elle renvoie vers une condition : "If you wish to match against the hostname, port, or query string, use a RewriteCond with the %{HTTP_HOST}, %{SERVER_PORT}, or %{QUERY_STRING} variables respectively."
Autre détail qui piège souvent : dans un fichier .htaccess, le chemin comparé n'a pas de / au début. On écrit donc ^produits$, pas ^/produits$.
Comment rediriger selon la valeur d'un paramètre ?
# .htaccess à la racine du site
RewriteEngine On
# /produits?marque=ancienne => /produits?marque=nouvelle
RewriteCond %{QUERY_STRING} (^|&)marque=ancienne(&|$)
RewriteRule ^produits$ /produits?marque=nouvelle [R=301,L]
(^|&) et (&|$) encadrent le paramètre : il peut être en première position ou après un &, et la valeur doit être exactement ancienne. Dans nos tests, /produits?page=2&marque=ancienne&tri=prix est bien redirigé, alors que ?marque=ancienne2 et ?sous-marque=ancienne ne le sont pas. Attention : la cible ayant sa propre chaîne de paramètres, page et tri sont perdus en route.
La comparaison tient compte des majuscules : ?MARQUE=Ancienne n'est pas redirigé. Ajoutez [NC] à la fin de la ligne RewriteCond pour l'accepter.
La règle publiée ici auparavant contenait & au lieu de &, une entité HTML ajoutée par l'éditeur du blog. La regex cherchait alors le texte & : la redirection fonctionnait pour ?marque=MAMARQUE seul, mais pas pour ?page=2&marque=MAMARQUE ni pour ?marque=MAMARQUE&page=2. Après un copier-coller depuis une page web, relisez chaque &.
Dans un site Laravel, ces règles vont dans public/.htaccess, avant la règle qui envoie tout vers index.php : celle-ci porte le drapeau [L], qui arrête le traitement des règles suivantes.
Garder, remplacer ou supprimer la chaîne de paramètres ?
C'est le point qui change le plus le résultat. Pour capturer une valeur, on utilise %1, %2... : ce sont les groupes entre parenthèses de la dernière RewriteCond qui a réussi.
# /fiche.php?id=42&utm_source=x => /produit/42, sans paramètres
RewriteCond %{QUERY_STRING} (^|&)id=([0-9]+)(&|$)
RewriteRule ^fiche\.php$ /produit/%2 [R=301,L,QSD]
Voici ce que nous avons obtenu pour la même demande /fiche.php?id=42&utm_source=x (ou /promo?utm_source=news pour la dernière ligne) :
| Cible de la règle | Adresse de redirection obtenue |
|---|---|
/produit/%2 sans drapeau |
/produit/42?id=42&utm_source=x (paramètres recopiés) |
/produit/%2 [QSD] |
/produit/42 |
/produit/%2? (point d'interrogation final) |
/produit/42 |
/boutique?source=promo [QSA] |
/boutique?source=promo&utm_source=news |
La documentation le confirme : sans chaîne de paramètres dans la cible, Apache recopie celle d'origine ; [QSD] la supprime (Apache 2.4.0 et plus) ; une cible qui contient un ? remplace l'ancienne chaîne, sauf avec [QSA] qui ajoute les anciens paramètres aux nouveaux.
301 ou 302 ?
[R] sans code renvoie une 302 (redirection temporaire) : "a 302 status code being used by default if none is specified". Nous l'avons vérifié. Pour un changement d'adresse définitif, celui d'une migration de site, écrivez [R=301,L], comme ci-dessus.
Pour vérifier une règle, curl affiche le code et l'adresse de destination sans suivre la redirection :
curl -s -o /dev/null -w "%{http_code} %{redirect_url}\n" "https://www.example.com/fiche.php?id=42&utm_source=x"
Et avec Nginx ?
Nginx n'a pas de .htaccess. Chaque paramètre est disponible dans une variable $arg_nom ("argument name in the request line"), et la chaîne complète dans $args.
# /etc/nginx/conf.d/site.conf, dans le bloc server
location = /produits {
if ($arg_marque = ancienne) {
return 301 /produits?marque=nouvelle;
}
try_files $uri /index.html;
}
location = /fiche.php {
if ($arg_id ~ ^[0-9]+$) {
return 301 /produit/$arg_id;
}
}
location = /promo {
rewrite ^ /boutique?source=promo redirect;
}
Résultats obtenus : /produits?marque=ancienne est redirigé, /produits et /produits?marque=nouvelle affichent la page normalement (grâce à try_files), ?marque=Ancienne n'est pas redirigé (valeur sensible à la casse). /fiche.php?id=42&utm_source=x donne /produit/42, sans paramètres : return n'ajoute rien de lui-même.
rewrite se comporte à l'inverse : la documentation précise que les anciens paramètres sont ajoutés après les nouveaux, sauf si la cible se termine par ?. /promo?utm_source=news devient donc /boutique?source=promo&utm_source=news, et rewrite ^ /produit/$arg_id? permanent; donne /produit/42 sans rien derrière. Évitez return 302 /boutique?source=promo&$args; : sans paramètre d'origine, l'adresse se termine par un & inutile.
Ces redirections font partie des vérifications d'une mise en ligne ou d'une refonte, avec la visibilité sur Google et l'hébergement. Pour une autre erreur de configuration serveur fréquente, voir No input file specified.
- Les paramètres se testent avec
RewriteCond %{QUERY_STRING}, jamais dans le motif de laRewriteRule. (^|&)nom=valeur(&|$)trouve le paramètre quelle que soit sa position.- Cible sans
?: paramètres recopiés ;[QSD]ou?final : supprimés ;[QSA]: ajoutés aux nouveaux. [R]seul donne une 302 : écrire[R=301,L]pour un changement définitif.
Testé avec Apache 2.4.69 et Nginx 1.31.6 (Docker), curl 8.10.1.
Questions fréquentes
Comment rediriger quelle que soit la position du paramètre dans l'URL ?
Encadrez-le avec (^|&) avant et (&|$) après dans la RewriteCond, par exemple (^|&)marque=ancienne(&|$). Dans nos tests, la règle a fonctionné pour ?marque=ancienne comme pour ?page=2&marque=ancienne&tri=prix, sans attraper ?sous-marque=ancienne.
Les paramètres de suivi (utm_source...) sont-ils conservés après la redirection ?
Oui si la cible ne contient pas de ? : Apache recopie toute la chaîne d'origine. Ajoutez [QSD], ou un ? à la fin de la cible, pour arriver sur une adresse propre.
La valeur du paramètre est-elle sensible aux majuscules ?
Oui, par défaut, dans Apache comme dans Nginx : ?marque=Ancienne n'a pas été redirigé dans nos tests. Avec Apache, le drapeau [NC] sur la ligne RewriteCond rend la comparaison insensible à la casse.
Où placer ces règles dans un site Laravel ?
Dans public/.htaccess, après RewriteEngine On et avant la règle qui envoie les requêtes vers index.php. Avec Nginx, dans le bloc server du site.