Comment structurer efficacement le maillage interne sur WordPress ?

Par Maxime Bénard

11 min de lecture

Comment structurer efficacement le maillage interne sur WordPress ?

Le maillage interne WordPress se règle d’habitude dans l’éditeur, lien par lien, ou avec une extension qui en pose à votre place. Pour la refonte de notre site, en cours de mise en production, on a fait autrement : les liens entre les pages, les redirections héritées de l’ancien site et les pages proposées sous chaque article sont décidés dans le code du thème, versionnés avec lui et testés. Le site tourne sur Bedrock avec un thème Timber/Twig écrit à la main, sans page builder et sans extension SEO.

Cet article raconte comment, avec les extraits réels du dépôt. Ce n’est pas la seule façon de faire un maillage interne sur WordPress, mais c’est celle qui nous a permis d’arriver à la recette avant bascule avec zéro lien interne qui ne réponde pas 200.

Pourquoi on décide le maillage dans le code et pas dans l’éditeur

Notre ancien site a vécu ce que vivent la plupart des blogs : des articles fusionnés, des pages de service renommées, des slashs finaux oubliés. Chaque changement laisse derrière lui des liens qui passent par une redirection, ou qui mènent vers un article dont l’ancre ne parle plus. Corriger ces liens à la main dans une centaine d’articles, c’est un travail qu’on refait à chaque réorganisation, et qu’on finit par ne plus faire.

La refonte reprend en plus le contenu de l’ancien site par une chaîne d’import rejouable. Tant que l’ancien site reste en ligne, les articles sont réimportés tels quels : une correction faite dans la base de la refonte sortirait du périmètre synchronisé. Il fallait donc corriger les liens sans toucher au contenu. D’où trois briques, toutes dans le thème :

  • une table de redirections versionnée, config/redirects.php, lue par src/Setup/Redirects.php ;
  • une réécriture des liens au rendu, src/Content/LinkTargets.php, qui s’appuie sur cette table ;
  • un choix des pages de service proposées sous chaque article, src/Content/RelatedServices.php, couvert par un test PHPUnit.

Des redirections versionnées qui l’emportent sur le contenu importé

L’ancien site gérait ses redirections avec une extension SEO. On les a reprises dans un simple tableau PHP : aujourd’hui 82 règles, 75 en 301 et 7 en 410. Chaque règle associe un chemin sans slash final à une cible et un code. Un exemple réel, qui concerne d’ailleurs cet article : un ancien billet sur la structure d’un blog WordPress a été fusionné ici.

php
'/blog/structurer-blog-wordpress-seo' => [
    'cible' => '/blog/structurer-le-maillage-interne-sur-wordpress/',
    'code' => 301,
],

Le point qui compte est l’ordre de priorité. Dans la base importée, les articles fusionnés gardent leur statut « publié ». Si la règle ne s’appliquait qu’en cas de 404, l’ancienne adresse continuerait de servir un doublon. Le commentaire de la classe Redirects le dit sans détour : une règle l’emporte sur le contenu servi à la même adresse, et un contenu à garder en ligne n’a simplement pas de règle. En complément, les 20 articles fusionnés sont passés en privé par un installateur, ce qui les sort du plan du site.

Deux autres décisions sont dans la même classe. Un code 410 sert la page 404 avec ce statut, sans redirection. Et la devinette de WordPress sur les 404 est coupée :

php
// Sur une 404, WordPress devine un contenu dont le slug commence
// pareil et y redirige : une adresse inconnue finit sur un article
// sans rapport, ce que Google requalifie en soft 404. Une 404 reste
// une 404 ; les redirections sont toutes explicites.
add_filter('do_redirect_guess_404_permalink', '__return_false');

C’est un réglage invisible dans le back-office, et c’est précisément le genre de comportement qu’on veut voir écrit quelque part plutôt que découvrir dans Search Console.

Les liens internes réécrits au rendu vers leur adresse finale

Une table de redirections règle le cas des visiteurs qui arrivent par une ancienne adresse. Elle ne règle pas les liens internes : un lien vers une adresse en 301 fonctionne, mais il fait passer chaque visite et chaque passage de robot par un saut. LinkTargets corrige ça au moment d’afficher un article repris de l’ancien site. Pour chaque lien qui vise le site, il interroge la même table que les redirections :

php
$rule = ($this->resolve)(rtrim($path, '/') ?: '/');

if (null !== $rule) {
    if (410 === $rule['code'] || '' === $rule['cible']) {
        return '';
    }

    return $this->relative($rule['cible']) . $suffix;
}

// Un chemin de page se termine par un slash ; un fichier (extension)
// reste tel quel.
$final = preg_match('/\.[a-z0-9]{2,5}$/i', $path) ? $path : rtrim($path, '/') . '/';

Trois cas, donc. Le slash final manquant est rétabli. Une adresse qui a une règle 301 est remplacée par sa cible. Une adresse partie pour de bon (410) perd sa balise <a> et garde son texte : la phrase reste lisible, le lien mort disparaît. Le contenu en base n’est pas touché, la prochaine synchronisation avec l’ancien site ne défait rien.

Le même mécanisme a servi à réparer des liens qui n’avaient jamais fonctionné. Notre article sur l’optimisation des requêtes SQL sous PrestaShop se terminait par une liste « Pour aller plus loin » dont cinq adresses n’avaient jamais existé. Plutôt que de retoucher l’article, on leur a donné une règle vers la page qui répond à la même question :

php
// Adresses qui n'ont jamais existé, liées depuis la liste « Pour aller
// plus loin » de l'article /blog/optimiser-requetes-sql-prestashop/ : la
// règle sert surtout au rendu (Content\LinkTargets), qui remplace chaque
// lien par la page qui répond à la même question. Même ancre, même sujet.
'/services/audit-prestashop' => [
    'cible' => '/nos-services/audit-de-performance-et-de-securite/',
    'code' => 301,
],

« Même ancre, même sujet » est la règle qui compte. Une redirection vers une page sans rapport avec l’ancre n’est pas un correctif, c’est un contresens que Google finit par ignorer. On a déjà écrit sur ce piège côté PrestaShop, dans les redirections SEO qui écrasent les pages natives.

Les services proposés sous chaque article, choisis sur les mots et testés

Sur notre site, ce sont les articles qui reçoivent le trafic et les liens externes. Les pages de service, elles, en reçoivent peu. Le maillage qui compte pour elles part donc des articles. Sous chaque article, RelatedServices propose trois pages. Chaque page proposable porte une liste de mots, en minuscules et sans accents :

php
'seo' => [
    'label' => 'SEO technique',
    'text' => 'Structure, Core Web Vitals, balisage : la moitié technique du référencement.',
    'url' => Routes::path('services') . 'referencement-seo-lyon/',
    'keywords' => ['seo', 'referencement', 'google', 'maillage', 'indexation', 'redirection', 'fiche produit', 'fiche-produit', 'search console', 'sitemap', 'codes http', 'codes-http', 'arborescence'],
],

Le calcul cherche ces mots dans le titre de l’article, son adresse, ses catégories et ses étiquettes. Une expression de plusieurs mots pèse 3, un mot seul pèse 2. Le mot « prestashop » désigne la moitié de nos articles : il ne départage rien, son score est ramené à 1. Quand le sujet n’en dit pas assez, un de nos quatre services principaux complète la liste, et celui qui complète change d’un article à l’autre (un crc32 du slug) pour que les quatre en profitent.

Ce choix est vérifié par un test. Celui-ci porte justement sur l’article que vous lisez :

php
yield 'seo article' => [
    'Structurer le maillage interne sur WordPress', 'structurer-le-maillage-interne-sur-wordpress', ['referencement'],
    ['SEO technique', 'Création et refonte de site'],
];

Si quelqu’un ajoute demain un mot trop générique à une page, et que cet article se met à proposer autre chose que le SEO technique et la refonte, le test casse avant la mise en ligne. C’est la différence avec une règle posée dans l’interface d’une extension : elle se relit dans une merge request, et elle se teste.

Plugin de maillage interne WordPress : pourquoi on n’en utilise pas

La question revient souvent, alors autant y répondre franchement : sur notre site, aucune extension de maillage interne, et aucune extension SEO. Les extensions qui insèrent des liens à partir de mots-clés résolvent un problème de volume (des centaines d’articles à relier sans y passer des semaines). Notre problème était autre : garder des liens exacts pendant une migration, sur un contenu réimporté plusieurs fois.

Pour ça, il nous fallait trois choses qu’un réglage en base ne donne pas : un historique (qui a changé quelle règle, et pourquoi, dans le commit), des tests exécutables sans WordPress, et une recette automatisée avant chaque bascule. La balise title, la description, l’adresse canonique, le plan du site et le JSON-LD sont écrits de la même façon par les classes de src/Seo/.

Ce n’est pas un choix universel. Sur un site éditorial tenu par une équipe qui publie tous les jours sans développeur, une extension reste plus pratique. Sur notre site, les liens des articles restent écrits à la main dans l’éditeur ; le code s’occupe de ce qui doit rester juste quand les adresses bougent.

La recette avant bascule : un crawl et des liens en 200 strict

Le maillage n’est bon que s’il est vérifié en HTTP. Le dossier tools/seo/ du dépôt contient trois contrôles, rejoués avant et juste après chaque bascule :

bash
# 1. Chaque adresse du plan du site source répond sur la cible (200, 301 lisible, ou 410)
tools/seo/compare-sitemaps.sh / https://mintfull.agency

# 2. Chaque règle de config/redirects.php : code, cible exacte, cible en 200, pas de chaîne
tools/seo/check-redirects.sh https://mintfull.agency

Le troisième, crawl.py, parcourt le plan du site de la refonte et contrôle chaque lien interne sans suivre les redirections : une 301 compte comme une erreur, seul un 200 passe. Le script vérifie aussi les titres en doublon, le h1 unique, la canonique, la description et le JSON-LD.

La recette locale du 17 septembre donnait les 117 adresses du plan du site de production toutes en 200, ou dans une redirection 301 identique à celle de l’ancien site, et 92 pages crawlées sans doublon de titre. Le contre-crawl du 22 septembre, sur 92 pages : zéro lien interne qui ne réponde pas 200, les cinq liens morts de l’article SQL compris, désormais pointés vers leur cible par LinkTargets. Le script dit où mène une redirection, pas si la cible répond à la même question que la source : ce jugement-là reste humain.

Comment faire un maillage interne WordPress qui tient dans le temps

Ce qu’on retient de cette refonte, applicable même sans écrire de PHP :

  • un lien interne vise l’adresse finale, slash compris, jamais une adresse qui répond 301 ;
  • une redirection mène vers la page qui répond à la même question que l’ancre, sinon c’est un 410 ;
  • les redirections et les pages retirées vivent dans une liste qu’on peut relire, pas dispersées dans des réglages ;
  • les articles, qui reçoivent le trafic, portent le maillage vers les pages de service ;
  • la vérification se fait en HTTP, sans suivre les redirections, avant et après chaque changement d’adresses.

Questions fréquentes sur le maillage interne WordPress

Comment faire un maillage interne sur WordPress ?

Dans l’éditeur, on sélectionne le texte et on colle l’adresse finale de la page cible, avec son slash. Ce qui fait la différence ensuite, c’est de revérifier ces liens en HTTP quand les adresses bougent : sur notre refonte, un crawl en 200 strict et une réécriture au rendu (LinkTargets) s’en chargent.

Faut-il un plugin de maillage interne sur WordPress ?

Pas forcément. Notre site n’en utilise aucun : les redirections sont dans config/redirects.php, les services proposés sous chaque article sont choisis par une classe testée. Une extension se justifie surtout quand beaucoup de contenus sont publiés sans développeur.

Que faire des liens internes vers une page supprimée ?

Si une page répond à la même question, on redirige en 301 et on pointe les liens directement vers elle. Sinon, la page répond 410 et le lien disparaît en gardant son texte : c’est ce que fait notre thème au rendu, sans toucher au contenu en base.

On conçoit et on maintient des sites WordPress écrits à la main, avec le même soin sur le SEO technique : voir notre offre de site web sur mesure à Lyon. Un site qui a perdu des liens ou des positions après une refonte ? Parlons-en.

Partager cet article
Articles

Des articles pour faire pousser vos idées

D'autres retours d'expérience et méthodes à mettre en pratique sur votre site.

Bon, on en discute ?

Demandez votre diagnostic de performance gratuit : un échange de 30 minutes, vos Core Web Vitals relevés sur mobile et une synthèse écrite de ce qui mérite d’être corrigé en premier.

Discuter de mes besoins