frFrench ▾
deGerman
enEnglish
esSpanish
hiHindi
itItalian
jaJapanese
koKorean
noNorwegian
zhChinese
Accueil Nouveautés Actualités Tutoriels Consommation Culture Vidéo Buzz Divers

Accents et ñ dans les slugs : pourquoi un article finit en 404

Publié le 06 novembre 2026
Mains d

Un article part en ligne, le titre est propre… et le lien public répond 404. Rien dans les journaux d’erreurs PHP, rien dans la base : l’article existe bel et bien. Le coupable tient en une lettre, le ñ de « La Niña ». C’est ce qui nous est arrivé sur ce blog début novembre 2026, et la cause est assez instructive pour mériter qu’on la démonte pièce par pièce.

Le principe est simple : l’adresse d’un article est fabriquée à partir de son titre, ce qu’on appelle un slug (ou alias). Si la fonction qui le fabrique laisse passer un caractère que le serveur web ne sait pas router, l’article devient injoignable alors que tout semble en ordre.

Le scénario : un slug qui garde son ñ

Notre fonction de génération d’alias remplaçait les accents un par un, avec une table écrite à la main : é devient e, à devient a, ç devient c, et ainsi de suite. Cette table couvrait bien le français, mais pas l’espagnol ni le portugais : ñ, í, ó, ú, ò, ö ou ã passaient tels quels. Le titre « La Niña : le risque économique caché derrière El Niño » produisait donc un alias contenant encore son ñ.

Côté serveur, le fichier .htaccess route les articles avec une règle de ce genre :

RewriteRule ^article/([a-zA-Z0-9-_]+)$ /index.php?page_theme=article&alias=$1 [L]

La classe [a-zA-Z0-9-_] n’accepte que des lettres ASCII, des chiffres, le tiret et le tiret bas. Le navigateur envoie le ñ encodé en %C3%B1, Apache le décode avant de comparer, et les deux octets UTF-8 obtenus ne font évidemment pas partie de la classe. La règle ne s’applique pas, aucune autre ne prend le relais, et c’est la page 404 qui répond.

Le détail qui aggrave tout : l’ancre de fin

Quelques jours plus tôt, nous avions ajouté le $ en fin de motif. Sans lui, la règle se contentait d’un début d’adresse : pour /article/la-niña-…, elle capturait la-ni et coupait silencieusement le reste. Selon la façon dont le CMS résout un alias partiel, on obtient alors une mauvaise page, une page en double aux yeux de Google, ou une 404 incompréhensible. L’ancre rend l’erreur franche, ce qui est mieux, mais elle ne corrige pas la source : c’est le slug qui doit être propre.

Publicité

Pourquoi strtolower ne vous sauvera pas

Une partie de nos remplacements ne visait que les minuscules, en comptant sur une mise en minuscules préalable. Or strtolower() ne traite que l’ASCII : depuis PHP 8.2, elle ne dépend même plus de la locale et laisse Ñ ou É intacts. Un titre écrit en capitales, « EL NIÑO », gardait donc son accent même après correction des minuscules. Il faut soit traiter explicitement les majuscules, soit utiliser mb_strtolower($titre, 'UTF-8').

UTF-8, latin1 et remplacements qui ne trouvent rien

Deux autres pièges guettent toute table de remplacement maison.

  • Le double encodage. Si un titre a transité par une connexion MySQL en latin1 alors que la base est en UTF-8 (ou l’inverse), le ñ devient ñ. Votre str_replace('ñ', 'n', …) ne trouve plus rien et le slug sort avec des débris. Vérifiez le jeu de caractères de la connexion (utf8mb4 partout) avant d’accuser la fonction.
  • Les expressions régulières sans le modificateur u. preg_replace('/[àâä]/', 'a', $s) travaille octet par octet : la classe contient six octets, et « à » (deux octets) devient « aa ». Avec /[àâä]/u, PCRE raisonne en caractères et le résultat est correct.

Slugifier proprement en PHP

Plutôt que d’allonger indéfiniment une table, mieux vaut s’appuyer sur la translittération Unicode, puis filtrer avec une liste blanche stricte.

La voie robuste : l’extension intl

function slugifier(string $titre): string
{
    // Translittère toute écriture en latin, puis en ASCII, et passe en minuscules
    $s = transliterator_transliterate('Any-Latin; Latin-ASCII; Lower()', $titre);
    // Liste blanche : tout ce qui n’est pas a-z ou 0-9 devient un tiret
    $s = preg_replace('/[^a-z0-9]+/', '-', $s);
    return trim($s, '-');
}

Cette version traite ñ, ß (qui devient ss), œ, ø ou ł, et même les écritures non latines, que Any-Latin romanise. Elle ne dépend d’aucune locale du serveur.

Les alternatives et leurs limites

  • iconv('UTF-8', 'ASCII//TRANSLIT', $s) dépend de la locale active : sans setlocale(LC_CTYPE, 'fr_FR.UTF-8'), la glibc rend « Ni?a », et sous Alpine (musl) le résultat est encore moins fiable.
  • Normalizer::normalize($s, Normalizer::FORM_D) suivi de preg_replace('/p{Mn}+/u', '', $s) retire bien les accents, mais laisse œ, æ, ß ou ø, qui ne sont pas des lettres accentuées au sens Unicode.
  • Une table maison reste acceptable si l’extension intl n’est pas disponible, à condition de la terminer par la même liste blanche : c’est elle qui garantit le résultat.

La vraie règle : le slug et la réécriture doivent parler la même langue

Le fond du problème, c’est qu’on avait deux définitions différentes de « caractère autorisé » : celle, implicite, de la fonction PHP et celle, explicite, du .htaccess. La règle à retenir est simple : la liste blanche du générateur de slug doit être identique à la classe de caractères de la règle de réécriture. Si le .htaccess accepte [a-z0-9-], la fonction doit finir par preg_replace('/[^a-z0-9]+/', '-', …), sans exception.

Petit conseil au passage : dans une classe, placez le tiret en dernier, [a-zA-Z0-9_-]. Écrit au milieu, il fonctionne par tolérance du moteur, mais il se lit comme une plage et finit tôt ou tard par piéger quelqu’un.

Prévenir plutôt que guérir

  • Un test avec des titres piégeux : « La Niña », « EL NIÑO », « Ça coûte 5 € », « Œuvre complète », « Straße », un titre en japonais. Chaque slug doit correspondre au motif de la règle Apache.
  • Un repli si le slug est vide : un titre composé uniquement de symboles donne une chaîne vide. Utilisez alors l’identifiant de l’article.
  • Un contrôle des alias existants : une requête du type SELECT alias FROM articles WHERE alias NOT REGEXP '^[a-z0-9-]+$' révèle en quelques secondes les adresses déjà cassées.
  • Une vérification après publication : un simple appel HTTP sur l’URL publique, qui doit répondre 200, aurait signalé le problème dès la mise en ligne.

Et pour le référencement ?

Google sait indexer des URL contenant des caractères Unicode, donc un ñ dans une adresse n’est pas interdit en soi. Mais une fois copiée dans un message ou un réseau social, l’adresse apparaît souvent sous sa forme encodée, avec des %C3%B1 peu engageants. Un slug ASCII, court et lisible reste le choix le plus sûr, à condition qu’il soit fabriqué par une fonction qui ne laisse rien passer.

Dans notre cas, la correction a tenu en trois gestes : compléter la translittération (minuscules et majuscules), corriger l’alias de l’article concerné en base, et garder l’ancre de fin dans le .htaccess pour que toute future erreur soit visible au lieu d’être silencieusement tronquée.

Tags
slug
url
accent
htaccess
utf-8
seo
Envoyer à un ami
Signaler cet article
A propos de l'auteur