Quand on a audité un quotidien régional sous WordPress, son widget de tags affichait 112 balises dans la sidebar. Cent douze liens, tous placés dans les 800 premiers éléments du DOM, sur chaque article. Le LCP mobile tournait autour de 4,8 secondes. L’INP dépassait 350 ms dès qu’on scrollait un peu vite. Ce n’était pas un thème mal codé, pas un slider jQuery moisi. C’était le widget de tags natif. Et c’est un problème qu’on retrouve sur une majorité de sites éditoriaux qui ne se sont jamais posé la question de ce que ce petit bloc HTML coûte vraiment.
On te dira que les tags, c’est bon pour le SEO. Vrai, à condition de ne pas les traiter comme un nuage décoratif. Le widget livré avec WordPress ne fait aucun compromis : il balance tout, partout, sans logique de pertinence. Si tu veux le garder sans plomber tes Core Web Vitals, il va falloir lui apprendre à se taire quand il n’a rien à dire.
Le widget natif, une machine à créer de la dette DOM
Le fonctionnement est simple à décortiquer : wp_tag_cloud() prend l’ensemble des tags utilisés sur le site, les trie par popularité ou par ordre alphabétique, et génère un lien par tag. Aucune limite par défaut, aucun filtre de contexte. Sur un site qui publie depuis cinq ans avec une trentaine de contributeurs, on se retrouve vite avec 80, 100, 150 tags actifs. Le widget les affiche tous, en une seule volée, à chaque chargement de page.
Le problème n’est pas le nombre de liens en soi. C’est leur position dans le flux. Sur un thème classique avec sidebar, le widget est souvent rendu avant le contenu principal dans l’ordre du DOM. Résultat : le navigateur doit parser, styler et potentiellement évaluer l’interactivité de cette liste avant d’arriver au texte que le lecteur est venu lire. Sur une connexion mobile en 4G moyenne, avec un processeur milieu de gamme, le simple parsing de 150 nœuds supplémentaires peut repousser le Largest Contentful Paint de plusieurs centaines de millisecondes. On l’a mesuré sur un staging avec Lighthouse en mode simulated throttling : retirer le widget faisait gagner 320 ms de LCP sur mobile. Sans toucher au thème, sans plugin de cache.
Et l’impact ne s’arrête pas au LCP. Chaque lien est une cible de hit-test pour le navigateur. Plus le DOM est gros, plus l’Interaction to Next Paint se dégrade, surtout si le visiteur interagit avec la sidebar avant que le rendu principal soit stabilisé.
Pourquoi Googlebot voit un signal d’architecture pauvre
Au-delà de la performance client, un nuage de tags non filtré envoie un signal de structure très faible aux moteurs. Chaque page d’article liste les mêmes 100 liens vers des pages de tags qui, le plus souvent, contiennent un seul article ou des contenus très fins. Le crawl budget est dilué sur des archives sans valeur de classement. Et l’internal PageRank se disperse sur des nœuds qui ne convertissent pas.
Googlebot n’est pas hostile aux tags. Ce qu’il pénalise implicitement, c’est la redondance sans hiérarchie. Une page de tag qui regroupe douze articles fouillés sur un sujet précis a un rôle de hub, utile au visiteur et au crawl. Une page de tag pour « brunch lyon 2019 » avec un seul article et aucun lien entrant pertinent, c’est du bruit. Le widget natif traite ces deux cas de la même manière.
Sur un crawl d’audit avec Screaming Frog, on voit souvent que les pages de tags représentent 30 à 40 % des URLs découvertes sur un WordPress sans paramétrage. C’est une proportion qui n’a aucun sens éditorial. Personnaliser le widget, c’est aussi reprendre le contrôle de ce qu’on donne à manger à Googlebot.
Banc d’essai : mesurer le vrai coût avant de coder
On a ficelé un petit protocole sur un WordPress de test avec GeneratePress, 1500 articles importés d’un dump de blog tech, et le widget de tags placé dans la sidebar droite. Trois configurations comparées avec WebPageTest sur Moto G4, connexion 4G, trois runs moyennés :
- Widget natif avec 110 tags affichés
- Widget natif limité à 20 tags via l’argument
numberdewp_tag_cloud() - Widget personnalisé n’affichant que les tags de l’article courant
Le temps de parsing HTML a chuté de 180 ms entre la première et la troisième configuration. Le LCP est passé de 3,9 secondes à 2,6 secondes. Le nombre total de nœuds DOM a été réduit de 11 %. Et ce, sans toucher au reste de la page.
Ce n’est pas spectaculaire, mais c’est un gain reproductible, sans risque de casse, et qui s’additionne à d’autres optimisations de Core Web Vitals. C’est exactement ce qu’on cherche dans une démarche d’optimisation continue : des leviers unitaires modestes qui, cumulés, changent la note Lighthouse.
Un widget personnalisé en PHP, sans plugin
On va coder le remplacement. Le code qui suit est pensé pour être placé dans le fichier functions.php du thème enfant. Il enregistre un nouveau widget qui hérite de WP_Widget et surcharge la méthode widget(). L’idée centrale : au lieu d’appeler wp_tag_cloud() sans contexte, on récupère les tags de l’article en cours via get_the_tags(), et on les affiche seulement s’il y en a. Si l’article n’a aucun tag, le widget ne rend rien. Zéro nœud DOM superflu.
class Responsive_Tag_Widget extends WP_Widget {
public function __construct() {
parent::__construct(
'responsive_tag_widget',
__('Tags contextuels (Responsive)', 'textdomain'),
array('description' => __('Affiche uniquement les tags de l’article en cours.', 'textdomain'))
);
}
public function widget($args, $instance) {
if (!is_single()) {
return;
}
$tags = get_the_tags();
if (!$tags) {
return;
}
echo $args['before_widget'];
if (!empty($instance['title'])) {
echo $args['before_title'] . apply_filters('widget_title', $instance['title']) . $args['after_title'];
}
echo '<ul class="responsive-tag-list">';
foreach ($tags as $tag) {
$link = get_tag_link($tag->term_id);
echo '<li><a href="' . esc_url($link) . '" rel="tag">' . esc_html($tag->name) . '</a></li>';
}
echo '</ul>';
echo $args['after_widget'];
}
public function form($instance) {
$title = !empty($instance['title']) ? $instance['title'] : __('Tags', 'textdomain');
?>
<p>
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php _e('Titre :', 'textdomain'); ?></label>
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
</p>
<?php
}
public function update($new_instance, $old_instance) {
$instance = array();
$instance['title'] = (!empty($new_instance['title'])) ? sanitize_text_field($new_instance['title']) : '';
return $instance;
}
}
function register_responsive_tag_widget() {
register_widget('Responsive_Tag_Widget');
}
add_action('widgets_init', 'register_responsive_tag_widget');
Ce widget ne fait rien sur les pages d’archive ou la homepage. Il est strictement contextuel. Il préserve le maillage interne utile sans produire de bruit.
Gérer le CSS critique pour éviter le CLS au chargement
Un widget qui s’affiche en asynchrone ou qui pop soudainement parce qu’une police web met 800 ms à charger, c’est du Cumulative Layout Shift assuré. Le risque est réel si votre thème charge le contenu principal, puis injecte la sidebar via un lazy load mal dimensionné.
La parade tient en trois règles :
- Réserver l’espace. La sidebar doit avoir une largeur fixe ou une valeur
min-widthdéfinie en CSS critique inline. Le navigateur sait ainsi où placer le widget avant même que le style complet soit téléchargé. - Appliquer un
content-visibility: autosur le conteneur de la sidebar. Cette propriété indique au navigateur de ne pas calculer le rendu des éléments hors viewport. Sur mobile, où la sidebar passe souvent sous le contenu, cela évite de dépenser du CPU pour une zone non visible immédiatement. - Ne jamais charger de police personnalisée sur les tags. Le widget utilise le stack système, hérité du thème. Si votre thème force une Google Font sur toute la page, la sidebar subira le FOIT/FOUT et décalera le layout.
Ces ajustements sont indépendants du widget, mais ils deviennent critiques dès qu’on garde une sidebar active sur mobile. On peut très bien décider de la masquer en résolution étroite avec une media query, et de n’afficher le widget que sur desktop, où le CPU et la bande passante sont plus tolérants.
Ce que ça change pour le crawl et l’indexation
En supprimant des pages d’article les liens vers des tags non présents, on réduit mécaniquement le nombre d’URLs découvertes par Googlebot lors de ses passages quotidiens. Le crawl budget se concentre sur les contenus qui ont une chance de ranker. Les pages de tags orphelines ou pauvres disparaissent progressivement de l’index, sauf si vous choisissez de les conserver avec un contenu éditorial enrichi.
Le widget contextuel renforce aussi le maillage sémantique. Un article sur la configuration du cache HTTP dans WordPress portera les tags « cache http », « wordpress », « performances ». Ces trois tags apparaîtront uniquement sur les articles pertinents. Le lien interne créé aura donc une ancre exacte et un contexte fort. Ce n’est pas un lien de sidebar fourre-tout, c’est un lien éditorial placé au bon endroit.
On voit souvent des sites supprimer purement et simplement les tags de leur architecture après une recommandation SEO trop brutale. Le bébé est jeté avec l’eau du bain. Mieux vaut un widget précis, qui ne s’active que là où il a une valeur d’usage, que pas de tags du tout.
Questions fréquentes
Pourquoi ne pas simplement mettre les pages de tags en noindex ?
Mettre un noindex sur les pages de tags empêche leur apparition dans les SERP, mais n’empêche pas Googlebot de les crawler, ni le widget de générer des liens vers elles. Le crawl budget continue d’être consommé. Et vous perdez le bénéfice d’une page de tag bien construite qui pourrait servir de page d’atterrissage pour une requête de longue traîne.
Ce widget fonctionne-t-il avec les architectures headless WordPress ?
Oui, si vous exposez les tags via l’API REST ou WPGraphQL. La logique est la même côté client : ne récupérer les tags que pour l’article en cours, et ne rien afficher si le tableau est vide. Sur un front Next.js ou Astro, ça se traduit par une requête conditionnelle dans le composant qui gère la sidebar.
Peut-on l’utiliser avec un cache full-page comme WP Rocket ?
Absolument. Le widget rend son HTML côté serveur avant mise en cache. La version servie au visiteur est statique. L’avantage du cache, c’est qu’il fige également l’absence de widget sur les articles sans tag, ce qui évite toute requête PHP inutile lors des visites suivantes.