Photo de Chris F sur Pexels

Les slugs. Ce ne sont pas de simples limaces comme une traduction rapide de l’anglais le laisserait paraître. Ils ont leur utilité dans un site web. Mais qu’est-ce donc ? Comment les générer ? Comment les considérer ? Car il faut savoir à quoi servent les slugs, et en fonction de ça, choisir la meilleure stratégie à adopter pour les utiliser. Car oui, ce n’est pas un sujet si simple que ça.


Partager l’article Slugs, comment les générer ? sur les réseaux sociaux


Les slugs sont de courts textes utilisés généralement dans les URL et doivent être compréhensible humainement. Ainsi, la ressource cibler par le slug aura une signification au regard des moteurs de recherche (coucou les avantages SEO) ainsi que de permettre un repérage et une mémorisation améliorée pour les utilisateurs.

Comme un slug apparaît dans des URL, il faut veiller à ce qu’il ne comporte pas de caractères interdits comme l’espace, les signes diacritiques, les esperluettes, etc. Dans les faits, le slug comporte uniquement les lettres ASCII (sans diacritiques donc), de préférence en minuscule (mais ce n’est pas une obligation), avec des chiffres autorisés, des points, des traits d’union (hyphen), des underscores et… c’est tout.

Dans quels cas trouvons-nous des slugs ?

Les slugs peuvent se rencontrer dans différents cas que je vous liste ici, mais cette liste est loin d’être exhaustive :

  • Attribut id des titres de balise h1h6 de différents niveaux dans un document formant des ancres ayant sens pour le SEO. Vous pouvez constatez ça sur le site de MDN par exemple avec cette page documentant la balise <script>.
  • Titre transposé en URL d’un article, d’une page, nom/prénom d’une personne, fiche produit, etc. Vous en avez un exemple avec l’article que vous êtes en train de lire.
  • Un sous-domaine peut très bien reprendre la logique de slug. Regardez par exemple le site de Renault à Dax du groupe EdenAuto, ce sous-domaine fait sens par rapport aux autres concessions du groupe et par rapport au site du groupe lui-même. On sait que c’est une concession Renault, située à Dax. Top pour le référencement.

Un slug, une ressource

Idéalement, un slug doit correspondre à une ressource, et une seule, ce qui implique qu’un type de slug doit être unique. Ou plutôt devrait. En effet, deux visions des choses s’opposent dans la façon de différencier une ressource qui aurait le même nom et donc qui donnerait le même slug, ce qui entrainerait un conflit de résolution.

Première solution : Si le nouveau slug à créer correspond déjà à un slug existant, on y ajoute un chiffre d’incrémentation à la fin avec un caractère séparateur (un trait d’union dans l’immense majorité des cas). Ce système présente un gros problème : il introduit une information qui n’a rien à voir avec la signification du slug.

Imaginons que vous mainteniez un site web d’actualité. Un premier article s’intitulant « Nos idées de cadeaux pour Noël » a déjà été publié (et son slug est donc nos-idees-de-cadeaux-pour-noel) et un rédacteur crée un nouvel article cette année portant le même titre. Avec ce système, le slug du nouvel article sera nos-idees-de-cadeaux-pour-noel-2 et là on se demande « C’est la deuxième partie d’un article déjà existant ? C’est une deuxième page ? », etc.

Ce n’est clairement pas la solution optimale, surtout qu’il faut faire attention en base de données pour s’assurer du bon fonctionnement de l’incrémentation.

Seconde solution : Ne pas utiliser de slug unique, mais à la place, utiliser un système additionnel d’identification. Un très bon exemple est le site de Stack overflow avec l’URL d’une question : https://stackoverflow.com/questions/30363301/unique-slug-issue-auto-increment sera la même que https://stackoverflow.com/questions/30363301/. Le slug a ici une porté uniquement significative, et non identifiante, cette dernière étant déléguée au jeton situé avant le slug. Très pratique si le titre de la question change, grâce au jeton, on sera toujours redirigé vers la bonne URL.

Maintenant que nous avons survoler les quelques généralités sur l’utilité et la manière de concevoir les slugs, voyons comment les implémenter, simplement, sans système d’incrémentation.

Implémentation PHP

L’excellent Cocur slugify vous permet de réaliser des slugs avec des ajustements possibles et des règles personnalisées. Si vous ne l’avez pas installé sur votre projet, je vous recommande vivement son installation.

Vous pouvez, si vos besoins ne sont pas trop poussés (pas besoin d’incrémentation), réaliser une petite fonction qui permet de réaliser vos propres slugs sans installer une bibliothèque en plus.

Je vous livre le code, tel quel :

function slugify(string $str, string $sep = '-'): string
{
    // il vous faut l’extension `intl` installé et activée, sinon ça ne fonctionne pas
    if (!extension_loaded('intl')) {
        throw new \RuntimeException(
            'Missing Intl extension. This is required to use ' . __FUNCTION__ 
        );
    }


    return trim(
        preg_replace(
            '/[^a-z0-9]+/',
            $sep,
            \transliterator_transliterate(
                "Any-Latin; Latin-ASCII; Lower()",
                $str
            )
        ),
        $sep
    );
}

Attention, si vous avez une version de PHP inférieure à la 5.4, l’extension intl ne possède pas la fonction de translitération.

Cette fonction est simple, efficace. Le premier argument prend la chaîne de caractère à convertir en slug, le deuxième argument précise le type de séparateur, par défaut un trait d’union.

Implémentation en base de données avec PostgreSQL

L’extension unaccent permet une translitération, son nom est trompeur car il fait penser à une fonction enlevant les signes diacritiques seulement. Il le fait bien sûr, mais il fait bien plus, comme la prise en charge des caractères ligaturés par exemple.

-- Version permettant de spécifier le caractères de séparation
DROP FUNCTION IF EXISTS slugify(TEXT, TEXT);
CREATE FUNCTION slugify(value TEXT, sep TEXT)
RETURNS TEXT AS $$
BEGIN
    -- Chargement de l'extension
    CREATE EXTENSION IF NOT EXISTS "unaccent";

    RETURN trim(
        both sep from regexp_replace(
            lower(unaccent(value)),
            '[^a-z0-9\\-]+',
            sep,
            'gi'
        )
    );
END;
$$ LANGUAGE plpgsql;

-- Version utilisant le tiret implicitement
DROP FUNCTION IF EXISTS slugify(TEXT);
CREATE FUNCTION slugify(value TEXT)
RETURNS TEXT AS $$
BEGIN
    RETURN slugify(value, '-');
END;
$$ LANGUAGE plpgsql;

Vérifions :

userdb=# SELECT slugify('Qui vole un œuf, vole un bœuf !');
            slugify             
--------------------------------
 qui-vole-un-oeuf-vole-un-boeuf

userdb=# SELECT slugify('Je suis complètement dingue de cet œuf !', '_');
                 slugify                 
-----------------------------------------
 je_suis_completement_dingue_de_cet_oeuf

Et Idéalement, cette fonction est à utiliser dans des déclencheurs (triggers), afin d’avoir toujours la correspondance titre/slug, ce qui peut donner par exemple :

CREATE FUNCTION set_slug_from_title() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
BEGIN
  NEW.slug := slugify(NEW.title);
  RETURN NEW;
END
$$;

CREATE TRIGGER "trg_slug_insert"
BEFORE INSERT ON "my_table_name"
FOR EACH ROW
WHEN (NEW.title IS NOT NULL AND NEW.slug IS NULL)
EXECUTE PROCEDURE set_slug_from_title();

Implémentation en base de données avec MySQL

C’est possible de réaliser des slugs depuis MySQL, mais de manière bien moins élégante qu’avec PostgreSQL.

Jugez par vous-même avec la solution proposée dans cet article et dont je vous fournis la solution ici :

CREATE FUNCTION toSlug(s NVARCHAR(500)) RETURNS NVARCHAR(500) DETERMINISTIC
 
RETURN REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
REPLACE(REPLACE(LOWER(TRIM(s)), 
':', ''), ')', ''), '(', ''), ',', ''), '\\', ''), '/', ''), '"', ''), '?', ''),
"'", ''), '&', ''), '!', ''), '.', ''), ' ', '-'), '--', '-'), '--', '-'),'ù','u'),
'ú','u'),'û','u'),'ü','u'),'ý','y'),'ë','e'),'à','a'),'á','a'),'â','a'),'ã','a'), 
'ä','a'),'å','a'),'æ','a'),'ç','c'),'è','e'),'é','e'),'ê','e'),'ë','e'),'ì','i'),
'í','i'),'ě','e'), 'š','s'), 'č','c'),'ř','r'), 'ž','z'), 'î','i'),'ï','i'),'ð','o'),
'ñ','n'),'ò','o'),'ó','o'),'ô','o'),'õ','o'),'ö','o'),'ø','o'),'%', '');

Cette solution présente plusieurs problèmes : ça ne concerne que les langues basées sur l’alphabet latin. Quid du grec ? Quid des langues utilisant l’alphabet cyrillique ? Quid de l’arménien ? Quid tout simplement des langues utilisant des idéogrammes ? Oui j’aime bien le mot « quid ». Cela impose de forcer une limite dans les caractères pouvant être utilisés. C’est dommage est cela oblige certainement à passer par la création du slug avant l’enregistrement en base de données.

Bref. Avec cette fonction MySQL, vous devriez pouvoir faire ceci :

SELECT toSlug("Petit essai avec des caractères diacritiques ou à virer àéìòù?/(");

Doit donner :

petit-essai-avec-des-caracteres-diacritiques-ou-a-virer-aeiou

C’est une solution convenable si vous êtes sûr de n’avoir que des caractères latin au sein de votre base.

Je vous laisse quelques liens pour vous laisser tenter votre chance avec des fonctions MySQL plus élaborées pour créer des slugs (à vos risques et périls) :

Conclusion

Je vous recommande vivement, si possible, de générer vos slugs via un trigger en base de données, si vous êtes sur une base de données digne de ce nom (PostgreSQL quoi, je suis objectif non ? ;-D)

Au fait, un dernier mot : avec SQLite, ce n’est pas possible nativement, mais je pense que vous vous en êtes douté.

Photo de Chris F sur Pexels