Accenti e ñ negli slug: perché un articolo restituisce 404
Un articolo viene pubblicato, il titolo è corretto… ma il link pubblico restituisce 404. Nessun errore nei log PHP, nessun problema nel database: l'articolo esiste. Il colpevole è una sola lettera, la ñ di «La Niña». È successo su questo blog all'inizio di novembre 2026 e vale la pena analizzarne la causa.
Il principio è semplice: l'indirizzo di un articolo nasce dal titolo ed è chiamato slug (o alias). Se la funzione che lo genera lascia passare un carattere che il server web non sa instradare, l'articolo diventa irraggiungibile anche se tutto sembra regolare.
Lo scenario: uno slug che conserva la ñ
La nostra funzione di generazione degli alias sostituiva gli accenti uno alla volta, usando una tabella manuale: é diventa e, à diventa a, ç diventa c e così via. La tabella copriva bene il francese, ma non lo spagnolo né il portoghese: ñ, í, ó, ú, ò, ö e ã rimanevano invariati. Il titolo «La Niña : le risque économique caché derrière El Niño» generava quindi un alias che conteneva ancora la ñ.
Sul server, il file .htaccess instrada gli articoli con una regola di questo tipo:
RewriteRule ^article/([a-zA-Z0-9-_]+)$ /index.php?page_theme=article&alias=$1 [L]La classe [a-zA-Z0-9-_] accetta soltanto lettere ASCII, cifre, trattini e trattini bassi. Il browser invia la ñ codificata come %C3%B1, Apache la decodifica prima del confronto e i due byte UTF-8 risultanti non appartengono alla classe. La regola non scatta, nessun'altra interviene e il server restituisce la pagina 404.
Il dettaglio che peggiora tutto: l'ancora finale
Qualche giorno prima avevamo aggiunto il $ alla fine del pattern. Senza di esso, la regola verificava solo l'inizio dell'indirizzo: per /article/la-niña-… catturava la-ni e troncava silenziosamente il resto. A seconda di come il CMS risolve un alias parziale, il risultato può essere una pagina sbagliata, un duplicato per Google oppure un 404 inspiegabile. L'ancora rende l'errore evidente, il che è meglio, ma non elimina la causa: bisogna ripulire lo slug.
Perché strtolower non basta
Alcune sostituzioni riguardavano solo le minuscole, contando su una conversione preliminare. Ma strtolower() gestisce solo ASCII: da PHP 8.2 non dipende neppure dalla locale e lascia Ñ o É invariati. Un titolo in maiuscolo, «EL NIÑO», manteneva quindi l'accento anche dopo aver corretto le minuscole. Occorre gestire esplicitamente le maiuscole oppure usare mb_strtolower($titre, 'UTF-8').
UTF-8, latin1 e sostituzioni inefficaci
Ci sono altre due insidie nelle tabelle di sostituzione artigianali.
- La doppia codifica. Se un titolo è passato attraverso una connessione MySQL latin1 mentre il database usa UTF-8 (o viceversa), ñ diventa
ñ. Il vostrostr_replace('ñ', 'n', …)non trova più nulla e lo slug contiene caratteri corrotti. Verificate prima il set di caratteri della connessione (utf8mb4ovunque). - Le espressioni regolari senza il modificatore
u.preg_replace('/[àâä]/', 'a', $s)lavora byte per byte: la classe contiene sei byte e «à» (due byte) diventa «aa». Con/[àâä]/u, PCRE ragiona per caratteri e il risultato è corretto.
Creare slug corretti in PHP
Invece di allungare all'infinito una tabella, conviene usare la traslitterazione Unicode e poi filtrare con una rigorosa lista di caratteri consentiti.
La soluzione robusta: l'estensione intl
function slugifier(string $titre): string
{
// Traslittera ogni scrittura in latino, poi in ASCII, e converte in minuscolo
$s = transliterator_transliterate('Any-Latin; Latin-ASCII; Lower()', $titre);
// Lista consentita: tutto ciò che non è a-z o 0-9 diventa un trattino
$s = preg_replace('/[^a-z0-9]+/', '-', $s);
return trim($s, '-');
}Questa versione gestisce ñ, ß (che diventa ss), œ, ø e ł, oltre alle scritture non latine romanizzate da Any-Latin. Non dipende dalla locale del server.
Alternative e limiti
iconv('UTF-8', 'ASCII//TRANSLIT', $s)dipende dalla locale attiva: senzasetlocale(LC_CTYPE, 'fr_FR.UTF-8'), glibc produce «Ni?a» e su Alpine (musl) il risultato è ancora meno affidabile.Normalizer::normalize($s, Normalizer::FORM_D)seguito dapreg_replace('/p{Mn}+/u', '', $s)rimuove gli accenti, ma lascia œ, æ, ß e ø, che in Unicode non sono lettere accentate.- Una tabella manuale resta accettabile se l'estensione intl non è disponibile, purché termini con la stessa lista di caratteri consentiti: è questa a garantire il risultato.
La regola fondamentale: slug e riscrittura devono usare gli stessi caratteri
Il problema di fondo era avere due definizioni diverse di «carattere consentito»: quella implicita della funzione PHP e quella esplicita del .htaccess. La regola è semplice: la lista dei caratteri consentiti dal generatore di slug deve coincidere con la classe della regola di riscrittura. Se .htaccess accetta [a-z0-9-], la funzione deve terminare con preg_replace('/[^a-z0-9]+/', '-', …), senza eccezioni.
Un consiglio: in una classe mettete il trattino alla fine, [a-zA-Z0-9_-]. In mezzo può funzionare per tolleranza del motore, ma sembra un intervallo e prima o poi confonde qualcuno.
Prevenire è meglio che correggere
- Test con titoli insidiosi: «La Niña», «EL NIÑO», «Ça coûte 5 €», «Œuvre complète», «Straße» e un titolo giapponese. Ogni slug deve corrispondere al pattern della regola Apache.
- Un ripiego per gli slug vuoti: un titolo composto solo da simboli produce una stringa vuota. Usate allora l'ID dell'articolo.
- Controllo degli alias esistenti: una query come
SELECT alias FROM articles WHERE alias NOT REGEXP '^[a-z0-9-]+$'rivela rapidamente gli indirizzi già difettosi. - Verifica dopo la pubblicazione: una semplice richiesta HTTP all'URL pubblico, che dovrebbe restituire 200, avrebbe segnalato subito il problema.
E per la SEO?
Google sa indicizzare URL con caratteri Unicode, quindi una ñ nell'indirizzo non è vietata di per sé. Ma quando viene copiato in un messaggio o sui social, l'indirizzo appare spesso codificato, con poco invitanti %C3%B1. Uno slug ASCII breve e leggibile resta la scelta più sicura, purché la funzione non lasci passare caratteri indesiderati.
Nel nostro caso sono bastati tre interventi: completare la traslitterazione (minuscole e maiuscole), correggere nel database l'alias dell'articolo e mantenere l'ancora finale nel .htaccess, così ogni errore futuro sarà visibile invece di essere troncato silenziosamente.
French
German
English
Spanish
Hindi
Italian
Japanese
Korean
Norwegian
Chinese

