Depuis que l'INP a remplacé le FID dans les Core Web Vitals (le 12 mars 2024), beaucoup de sites WordPress autrefois « verts » sont passés au rouge. L'INP mesure la réactivité réelle aux interactions, et sur WordPress, ce sont les plugins et le JavaScript tiers qui plombent la performance. Voici comment diagnostiquer et optimiser l'INP, avec du code concret.
Je suis Tommy Bordas, développeur full-stack depuis plus de 10 ans à Nantes, spécialisé WordPress/WooCommerce et performance web. Cet article condense ce que j'applique en audit chez mes clients pour faire repasser l'INP au vert.
INP vs FID : ce qui a vraiment changé
Le FID (First Input Delay) ne mesurait qu'une chose : le délai entre la première interaction de l'utilisateur et le moment où le navigateur commençait à la traiter. Il ignorait tout le reste de la visite, et il ne tenait même pas compte du temps de traitement du gestionnaire ni du rendu qui suit. C'était une métrique indulgente, facile à garder dans le vert.
L'INP (Interaction to Next Paint) est bien plus sévère. Il observe toutes les interactions de la visite (clics, taps, frappes clavier), mesure pour chacune la latence complète (délai d'entrée + temps de traitement + délai de présentation jusqu'au prochain rendu visuel) et retient (à peu près) la pire. Un seul handler lent suffit donc à dégrader le score de toute la page.
| Métrique | Mesure | Portée |
|---|---|---|
| FID (déprécié) | Délai d'entrée de la 1re interaction | Une seule interaction |
| INP | Latence complète jusqu'au prochain paint | Pire interaction de la visite |
Concrètement : un site « FID vert » pouvait masquer un menu mobile qui mettait 400 ms à s'ouvrir au 3e clic. Avec l'INP, ce menu fait basculer la page dans le rouge.
À retenir : le passage FID → INP, officialisé le 12 mars 2024, n'est pas un simple renommage. On est passé d'une mesure du premier contact à une mesure de la réactivité soutenue sur toute la session. Le main thread bloqué, qu'on tolérait avant, coûte désormais des positions dans Google.
Les seuils officiels des Core Web Vitals
Google évalue chaque métrique sur le 75e centile des chargements de page, sur mobile et desktop séparément. Pour passer au vert, il faut donc que 75 % des visites respectent le seuil « bon ».
| Métrique | Bon | À améliorer | Mauvais |
|---|---|---|---|
| INP | ≤ 200 ms | 200-500 ms | > 500 ms |
| LCP | ≤ 2,5 s | 2,5-4 s | > 4 s |
| CLS | ≤ 0,1 | 0,1-0,25 | > 0,25 |
L'INP est aujourd'hui le Core Web Vital le plus difficile à tenir sur WordPress, justement parce qu'il dépend de la quantité de JavaScript exécuté sur le main thread, domaine où WordPress excelle… dans le mauvais sens.
Pourquoi WordPress souffre particulièrement de l'INP
Un WordPress de production moyen charge le JavaScript de 15 à 25 plugins : sliders, popups, analytics, chat, A/B testing, formulaires, cookies, social… Chacun ajoute des écouteurs d'événements et des longues tâches (long tasks > 50 ms) sur le main thread. Trois facteurs aggravent le problème :
- jQuery omniprésent. Beaucoup de thèmes et plugins reposent encore sur jQuery et ses plugins, qui s'exécutent de façon synchrone et monopolisent le thread.
- Scripts tiers non maîtrisés. Tag managers, pixels publicitaires, widgets de chat : leur code est injecté tel quel, sans découpage, et déclenche des tâches longues juste au moment où l'utilisateur veut cliquer.
- DOM surchargé. Les page builders (Elementor, Divi, WPBakery) génèrent des arbres DOM de plusieurs milliers de nœuds. Plus le DOM est gros, plus chaque recalcul de style et chaque rendu déclenchés par une interaction coûtent cher.
Diagnostic INP : lab vs terrain
Première règle : ne jamais optimiser à l'aveugle. Et surtout, distinguer deux types de données qui ne disent pas la même chose.
| Type | Source | Ce que ça mesure | Limite |
|---|---|---|---|
| Lab (synthétique) | Lighthouse, DevTools, onglet « Analyser » de PSI | Une exécution contrôlée, sur une machine donnée | Ne reflète pas vos vrais utilisateurs |
| Terrain (field / CrUX) | Chrome UX Report, PageSpeed Insights, RUM | Les vraies interactions des 28 derniers jours | Latence d'agrégation, pas de détail par session |
Lighthouse ne donne pas de score INP en lab, car l'INP a besoin de vraies interactions humaines pour exister. Le score qui compte pour le SEO vient du terrain (CrUX), visible en haut de PageSpeed Insights. Le lab sert à reproduire et déboguer ; le terrain sert à valider.
Repérer les longues tâches avec PerformanceObserver
La PerformanceObserver API révèle, dès le chargement, les tâches qui bloquent le main thread plus de 50 ms.
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > 50) {
console.warn(`Long task: ${Math.round(entry.duration)}ms`, entry);
}
}
}).observe({ type: 'longtask', buffered: true });
Attribuer l'INP à une interaction précise
Pour savoir quel élément et quelle phase (input delay, processing, presentation) plombent l'INP, la lib officielle web-vitals expose l'attribution complète :
import { onINP } from 'web-vitals/attribution';
onINP((metric) => {
const a = metric.attribution;
console.log('INP', metric.value, 'ms');
console.log('Cible :', a.interactionTarget);
console.log('Délai d\'entrée :', a.inputDelay);
console.log('Traitement :', a.processingDuration);
console.log('Présentation :', a.presentationDelay);
}, { reportAllChanges: true });
Croisez ces données avec l'onglet Performance de Chrome DevTools en throttling CPU 4× et réseau Slow 4G (pour simuler un mobile d'entrée de gamme), et avec le diagnostic INP de PageSpeed Insights. Vous obtenez le coupable exact : le script, le handler, la phase.
Correctif n°1 : découper les longues tâches (yielding)
Une fonction qui traite 1 000 éléments d'un coup monopolise le thread et fait grimper l'INP. On la découpe en cédant la main au navigateur entre les lots, ce qui lui laisse traiter les interactions en attente.
async function processInChunks(items, handler) {
for (let i = 0; i < items.length; i++) {
handler(items[i]);
// Cède la main tous les 50 éléments pour rester réactif
if (i % 50 === 0) {
await yieldToMain();
}
}
}
function yieldToMain() {
// scheduler.yield() : Chrome/Edge et Firefox (depuis août 2025).
// Pas encore dans Safari → fallback setTimeout.
if ('scheduler' in window && 'yield' in scheduler) {
return scheduler.yield();
}
return new Promise((resolve) => setTimeout(resolve, 0));
}
scheduler.yield() est supérieur au vieux truc du setTimeout(0) : la continuation reprend en priorité, avant les autres tâches en file, donc le travail ne se fait pas distancer par d'autres scripts. Attention toutefois : l'API n'est pas encore Baseline (Safari ne l'implémente pas à ce jour), d'où le fallback ci-dessus.
Correctif n°2 : différer le JavaScript non critique
Tout ce qui n'est pas nécessaire au premier rendu doit être chargé après que la page soit interactive. En PHP/WordPress, on force l'attribut defer sur les scripts non critiques via le filtre script_loader_tag, à placer dans le functions.php du thème ou un plugin maison.
add_filter('script_loader_tag', function ($tag, $handle) {
// Handles tels qu'enregistrés via wp_enqueue_script()
$defer = ['chat-widget', 'analytics', 'ab-testing', 'social-share'];
if (in_array($handle, $defer, true) && strpos($tag, ' defer') === false) {
return str_replace(' src', ' defer src', $tag);
}
return $tag;
}, 10, 2);
Le defer télécharge le script en parallèle mais n'exécute son code qu'une fois le HTML parsé, sans bloquer le rendu ni les premières interactions. Vérifiez les $handle exacts dans le code source de la page (view-source) ou via wp_print_scripts.
Correctif n°3 : retarder les scripts tiers jusqu'à l'interaction
Un chat, un widget social ou une carte interactive n'ont aucune raison de se charger avant que l'utilisateur n'en ait besoin. On les déclenche au premier scroll, clic ou frappe, ce qui retire leur coût du chemin critique et de l'INP initial.
const loadOnInteraction = (loader) => {
const events = ['scroll', 'pointerdown', 'keydown'];
const fire = () => {
loader();
events.forEach((e) => window.removeEventListener(e, fire));
};
events.forEach((e) =>
window.addEventListener(e, fire, { once: true, passive: true })
);
};
loadOnInteraction(() => loadChatWidget());
Sur WordPress, le plugin WP Rocket (option « Delay JavaScript execution ») ou Perfmatters automatisent cette technique sans code, plugin par plugin. Pratique quand on ne peut pas toucher chaque script tiers à la main.
Correctif n°4 : alléger le DOM et les écouteurs
L'INP grimpe aussi quand le navigateur doit recalculer des styles sur un DOM énorme à chaque interaction. Deux leviers :
- Réduire la taille du DOM : limitez les sections imbriquées des page builders, supprimez les wrappers inutiles. Visez moins de 1 500 nœuds ; Lighthouse alerte au-delà de ~800.
- Déléguer les événements : au lieu d'attacher un handler à 200 boutons, attachez-en un seul au conteneur parent et lisez
event.target. Moins d'écouteurs = moins de mémoire et des tâches plus courtes.
// Délégation : un handler pour toute une liste
document.querySelector('.product-grid').addEventListener('click', (e) => {
const btn = e.target.closest('.add-to-cart');
if (btn) addToCart(btn.dataset.productId);
});
La checklist INP sur WordPress
- Mesurer le terrain d'abord (CrUX / PageSpeed Insights), pas seulement le lab
- Auditer les long tasks (PerformanceObserver + DevTools CPU 4× / Slow 4G)
- Attribuer chaque INP à une interaction (
web-vitals/attribution) - Supprimer ou remplacer les plugins JS les plus lourds
-
defersur tout le JavaScript non critique (filtrescript_loader_tag) - Charger les scripts tiers à la première interaction
- Découper les traitements longs avec
scheduler.yield()(+ fallback Safari) - Alléger le DOM et déléguer les écouteurs d'événements
- Re-mesurer après 28 jours pour voir le terrain se mettre à jour
Pour aller plus loin
Sur l'optimisation globale d'une boutique en ligne (requêtes SQL, panier, cache fragmenté), voir aussi : Optimisation des performances WooCommerce.
Votre site WordPress est passé au rouge sur les Core Web Vitals, ou l'INP refuse de redescendre sous 200 ms ? Demandez un audit de performance : je trouve les longues tâches et je les corrige.