Aksenter og ñ i slugs: Derfor ender artikler med 404
En artikkel publiseres, tittelen ser riktig ut … men den offentlige lenken gir 404. Ingen feil i PHP-loggene eller databasen: artikkelen finnes. Synderen er én bokstav, ñ i « La Niña ». Dette skjedde på bloggen vår tidlig i november 2026, og årsaken er verdt å undersøke grundig.
Prinsippet er enkelt: Artikkeladressen lages av tittelen, som en slug (eller et alias). Hvis funksjonen slipper gjennom et tegn som webserveren ikke kan rute, blir artikkelen utilgjengelig selv om alt ellers ser riktig ut.
Scenarioet: En slug som beholder ñ
Funksjonen vår erstattet aksenttegn enkeltvis med en håndskrevet tabell: é ble e, à ble a, ç ble c og så videre. Tabellen dekket fransk, men ikke spansk eller portugisisk: ñ, í, ó, ú, ò, ö og ã ble beholdt. Tittelen « La Niña : le risque économique caché derrière El Niño » ga dermed et alias som fortsatt inneholdt ñ.
På serveren ruter filen .htaccess artiklene med en regel som denne:
RewriteRule ^article/([a-zA-Z0-9-_]+)$ /index.php?page_theme=article&alias=$1 [L]Tegnklassen [a-zA-Z0-9-_] godtar bare ASCII-bokstaver, tall, bindestrek og understrek. Nettleseren sender ñ kodet som %C3%B1, og Apache dekoder det før sammenligning. UTF-8-bytene som oppstår, inngår ikke i tegnklassen. Regelen matcher ikke, ingen annen regel overtar, og serveren svarer med 404.
Detaljen som forsterker feilen: Sluttankeret
Noen dager tidligere hadde vi lagt til $ på slutten av mønsteret. Uten dette krevde regelen bare treff i starten av adressen: For /article/la-niña-… fanget den opp la-ni og kuttet resten uten varsel. Avhengig av hvordan CMS-et håndterer delvise aliaser, kan resultatet bli feil side, en duplikatside i Googles øyne eller en uforståelig 404. Sluttankeret gjør feilen tydelig, men løser ikke årsaken: Sluggen må ryddes opp.
Hvorfor strtolower ikke redder deg
Noen av erstatningene våre gjaldt bare små bokstaver fordi vi regnet med at teksten først ble gjort om til små bokstaver. Men strtolower() håndterer bare ASCII. Siden PHP 8.2 er funksjonen heller ikke avhengig av lokalinnstillingene og lar Ñ og É stå urørt. En tittel skrevet med store bokstaver, « EL NIÑO », beholdt derfor aksenten selv etter at småbokstavene var rettet. Du må enten håndtere store bokstaver eksplisitt eller bruke mb_strtolower($titre, 'UTF-8').
UTF-8, latin1 og erstatninger som ikke treffer
To andre feller finnes i hjemmelagde erstatningstabeller.
- Dobbel tegnkoding. Hvis en tittel har gått gjennom en MySQL-tilkobling med latin1 mens databasen bruker UTF-8 (eller omvendt), kan ñ bli
ñ. Da finner ikkestr_replace('ñ', 'n', …)noen treff, og sluggen inneholder ødelagte tegn. Kontroller tegnsettet i tilkoblingen (utf8mb4overalt) før du skylder på funksjonen. - Regulære uttrykk uten modifikatoren
u.preg_replace('/[àâä]/', 'a', $s)arbeider byte for byte: Klassen inneholder seks byte, og « à » (to byte) blir « aa ». Med/[àâä]/utolker PCRE tegn, og resultatet blir riktig.
Slik lager du rene slugs i PHP
I stedet for å utvide tabellen i det uendelige bør du bruke Unicode-translitterering og deretter filtrere med en streng tillattliste.
Den robuste løsningen: intl-utvidelsen
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, '-');
}Denne varianten håndterer ñ, ß (som blir ss), œ, ø og ł, og også ikke-latinske skriftsystemer som Any-Latin romaniserer. Den er uavhengig av serverens lokalinnstillinger.
Alternativer og begrensninger
iconv('UTF-8', 'ASCII//TRANSLIT', $s)avhenger av aktiv lokalinnstilling. Utensetlocale(LC_CTYPE, 'fr_FR.UTF-8')kan glibc gi « Ni?a », og under Alpine (musl) er resultatet enda mindre pålitelig.Normalizer::normalize($s, Normalizer::FORM_D)etterfulgt avpreg_replace('/p{Mn}+/u', '', $s)fjerner aksenter, men beholder œ, æ, ß og ø, som ikke regnes som aksentbokstaver i Unicode.- En egen tabell er fortsatt et alternativ hvis intl ikke er tilgjengelig, men den må avsluttes med samme tillattliste. Det er denne som garanterer resultatet.
Hovedregelen: Slug og omskrivingsregel må bruke samme tegnsett
Rotårsaken var at vi hadde to ulike definisjoner av « tillatt tegn »: en implisitt i PHP-funksjonen og en eksplisitt i .htaccess. Husk denne regelen: Tillattlisten i slug-generatoren må samsvare med tegnklassen i omskrivingsregelen. Hvis .htaccess godtar [a-z0-9-], må funksjonen avsluttes med preg_replace('/[^a-z0-9]+/', '-', …), uten unntak.
Et lite tips: Sett bindestreken sist i tegnklassen, som i [a-zA-Z0-9_-]. Midt i klassen kan den fungere fordi motoren tolererer det, men den kan tolkes som et intervall og skape problemer senere.
Forebygging er bedre enn reparasjon
- Test med vanskelige titler: « La Niña », « EL NIÑO », « Ça coûte 5 € », « Œuvre complète », « Straße » og en japansk tittel. Hver slug må matche mønsteret i Apache-regelen.
- Reserveverdi for tom slug: En tittel som bare består av symboler, gir en tom streng. Bruk da artikkelens ID.
- Kontroller eksisterende aliaser: En spørring som
SELECT alias FROM articles WHERE alias NOT REGEXP '^[a-z0-9-]+$'finner raskt adresser som allerede er ødelagt. - Kontroller etter publisering: En enkel HTTP-forespørsel til den offentlige URL-en, som skal svare 200, ville avslørt problemet med én gang.
Hva med søkemotoroptimalisering?
Google kan indeksere URL-er med Unicode-tegn, så ñ i en adresse er ikke forbudt i seg selv. Men når lenken kopieres til en melding eller et sosialt nettverk, vises den ofte kodet, med lite innbydende %C3%B1. En kort og lesbar ASCII-slug er fortsatt det tryggeste valget, forutsatt at funksjonen ikke slipper gjennom uønskede tegn.
Hos oss besto rettingen av tre grep: fullføre translittereringen for både små og store bokstaver, rette aliaset i databasen og beholde sluttankeret i .htaccess, slik at fremtidige feil blir synlige i stedet for å bli kuttet uten varsel.
Norwegian
French
German
English
Spanish
Hindi
Italian
Japanese
Korean
Chinese


