Développement Web

Optimiser l’INP (Interaction to Next Paint) sous 200 ms

13 min de lecture

Sur le catalogue de la coopérative Niani, trois boutons permettent de filtrer les articles : paniers, textiles, beurre de karité. Sur votre ordinateur, le clic est instantané. Sur le téléphone d’un client à Abidjan, il se passe presque une demi-seconde entre l’appui et le changement à l’écran — assez pour qu’il appuie une deuxième fois, croyant avoir raté son geste. Ce délai porte un nom : l’Interaction to Next Paint. Cette métrique mesure la réactivité réelle de votre page. Ce tutoriel vous apprend à la ramener sous 200 millisecondes, là où l’interface redevient vive.

📍 Guide principal : Core Web Vitals et performance web : le guide — la vue d’ensemble des trois métriques.

🎯 Ce que vous allez apprendre

  • Comprendre ce que mesure l’INP et pourquoi il diffère d’un simple temps de chargement.
  • Décomposer une interaction en trois phases : délai d’entrée, traitement, rendu.
  • Découper les tâches longues et rendre la main au navigateur avec scheduler.yield().
  • Séparer le retour visuel immédiat du travail lourd différé.
  • Diagnostiquer l’interaction coupable avec les outils de développement et web-vitals.

🛠️ Ce que vous allez construire

Vous partez du filtre de catégories de Niani qui bloque l’interface pendant près de 400 millisecondes à chaque clic, et vous le transformez en une interaction fluide sous les 200 millisecondes. Vous gardez exactement la même fonctionnalité — filtrer les produits — mais l’utilisateur reçoit une réponse instantanée.

Prérequis

  • Des bases en JavaScript : écouteurs d’événements, fonctions, async/await.
  • Savoir ouvrir l’onglet Performance des outils de développement — voir Mesurer la performance web.
  • Une page avec au moins une interaction non triviale (filtre, menu, onglet, formulaire).
  • Niveau : intermédiaire. Si vous savez écrire element.addEventListener, vous suivrez.
  • ⏱️ Temps estimé : ~45 minutes.

Étape 1 — Comprendre ce que mesure l’INP

L’INP n’est pas un temps de chargement : c’est une mesure de réactivité. Pendant toute la visite, le navigateur observe la latence de chaque clic, chaque appui tactile, chaque frappe au clavier — et retient la pire (ou presque la pire) de ces interactions. La latence, ici, c’est le délai entre le geste de l’utilisateur et le moment où l’écran montre enfin le résultat de ce geste.

Les seuils, mesurés au 75ᵉ centile : l’INP est bon à 200 millisecondes ou moins, à améliorer entre 200 et 500, médiocre au-delà de 500. Notez ce qui ne compte pas : le défilement, le survol et le zoom ne sont pas des interactions au sens de l’INP. Seuls les clics, les appuis tactiles et les frappes clavier sont mesurés.

L’INP a remplacé l’ancienne métrique First Input Delay le 12 mars 2024. La différence est de taille : le FID ne mesurait que le délai de la première interaction, pas le temps de traitement ni le rendu. L’INP, lui, chronomètre l’interaction complète, de bout en bout, sur toute la visite. C’est beaucoup plus exigeant, et beaucoup plus représentatif de ce que ressent l’utilisateur.

Point d’étape — Vous savez que l’INP mesure la latence des interactions (clic, tap, clavier), retient la pire, et vise 200 ms. Le défilement n’entre pas en jeu.

Étape 2 — Décomposer une interaction en trois phases

Comme pour le LCP, on ne corrige bien que ce qu’on sait décomposer. Une interaction se déroule en trois phases successives, et identifier celle qui domine vous dit quoi corriger.

La première est le délai d’entrée : le temps entre le geste de l’utilisateur et le début de l’exécution de votre gestionnaire d’événement. Si le fil principal est déjà occupé par une autre tâche au moment du clic, le navigateur doit attendre qu’elle finisse — l’interaction patiente avant même de commencer. La deuxième est le temps de traitement : l’exécution de vos gestionnaires (filtrer, recalculer, modifier le DOM). La troisième est le délai de rendu : le temps que met le navigateur à calculer la mise en page et à peindre la nouvelle image.

Dans le cas du filtre de Niani, le coupable typique est le temps de traitement : à chaque clic, le code parcourt tous les produits, en reconstruit l’affichage et réécrit une grosse portion du DOM, le tout d’un seul bloc synchrone. Pendant ces 400 millisecondes, le navigateur ne peut rien peindre — l’interface est gelée.

Point d’étape — Vous pouvez nommer les trois phases. Regardez votre interaction dans l’onglet Performance : la barre la plus longue est-elle le délai d’entrée, le traitement ou le rendu ?

Étape 3 — Trouver l’interaction coupable

Avant d’optimiser, isolons la pire interaction. Les outils de développement le font très bien. Ouvrez l’onglet Performance, lancez un enregistrement, cliquez sur le filtre, arrêtez. Dans la trace, la section Interactions montre la durée de votre clic et la tâche longue qui l’a provoquée. Une tâche qui dépasse 50 millisecondes est dite « longue » et mérite votre attention.

Pour aller plus loin, la bibliothèque web-vitals en version d’attribution vous livre l’élément exact et la phase responsable, directement chez vos vrais utilisateurs.

import { onINP } from 'https://unpkg.com/web-vitals@5/attribution?module';

onINP(function (metric) {
  // attribution.interactionTarget pointe l'element clique
  // attribution.inputDelay, processingDuration, presentationDelay
  // detaillent les trois phases
  console.log('INP', metric.value, metric.attribution);
});

Cette attribution est précieuse : plutôt que de deviner, vous savez si le temps se perd avant, pendant ou après votre gestionnaire — et sur quel élément. Sur Niani, elle confirmera que le clic sur un bouton de filtre déclenche un long traitement synchrone.

Point d’étape — Vous avez identifié l’interaction la plus lente et la phase qui domine. Pour le filtre, ce sera le temps de traitement.

Étape 4 — Donner un retour visuel immédiat

Le principe fondateur de l’optimisation INP : l’utilisateur doit voir quelque chose changer tout de suite, même si le travail complet prend un instant de plus. Un bouton qui s’active visuellement dès le clic donne une sensation de réactivité, alors qu’une interface figée donne une sensation de panne — même pour une durée identique.

Concrètement, on sépare deux choses jusqu’ici mêlées : le retour visuel (changer l’état du bouton actif) et le travail lourd (filtrer et réafficher les produits). Le premier doit être quasi instantané ; le second peut attendre la prochaine occasion.

// Avant : tout est fait d'un bloc, l'interface gele
function onFiltre(categorie) {
  filtrerEtAfficherProduits(categorie); // 400 ms de blocage
  marquerBoutonActif(categorie);
}

Tel quel, le navigateur exécute les 400 millisecondes avant de pouvoir peindre quoi que ce soit. Le bouton ne s’active visuellement qu’à la toute fin. C’est ce que l’étape suivante corrige en rendant la main au navigateur entre les deux.

Point d’étape — Vous distinguez clairement le retour visuel du travail lourd dans votre gestionnaire. C’est la base de la correction.

Étape 5 — Rendre la main au navigateur avec scheduler.yield()

La technique clé s’appelle « céder le fil » (yielding) : on découpe le traitement long en morceaux et, entre chaque morceau, on rend brièvement la main au navigateur pour qu’il puisse peindre et répondre. L’API moderne pour cela est scheduler.yield(), disponible dans les Chromium récents, avec un repli simple sur setTimeout pour les autres navigateurs.

// On affiche le retour visuel, on cede le fil, puis on travaille
async function onFiltre(categorie) {
  marquerBoutonActif(categorie);   // retour visuel immediat
  await cederLeFil();              // le navigateur peut peindre le bouton
  filtrerEtAfficherProduits(categorie); // travail lourd ensuite
}

function cederLeFil() {
  if ('scheduler' in window && 'yield' in scheduler) {
    return scheduler.yield();
  }
  return new Promise(function (resolve) { setTimeout(resolve, 0); });
}

Que se passe-t-il ? Le bouton s’active, le code cède le fil, le navigateur en profite pour peindre cet état actif (l’utilisateur voit une réaction immédiate), puis le travail de filtrage reprend. L’INP enregistré chute, car la phase de rendu qui compte — celle qui répond au geste — survient presque tout de suite.

Pour un traitement vraiment lourd, on va plus loin : on découpe la boucle de filtrage elle-même et on cède le fil toutes les quelques millisecondes, pour qu’aucune tâche ne dépasse 50 millisecondes. Le navigateur reste alors réactif pendant toute l’opération.

Point d’étape — Votre gestionnaire cède le fil après le retour visuel. Re-mesurez l’INP de l’interaction : il devrait déjà passer sous 200 ms pour le clic lui-même.

Étape 6 — Réduire le travail de fond

Céder le fil masque le problème côté ressenti ; le réduire le résout à la racine. Deux leviers complètent la manœuvre. D’abord, en faire moins : un filtre n’a pas besoin de reconstruire tout le DOM. Masquer/afficher des cartes existantes avec une classe CSS est bien moins coûteux que de tout recréer. Ensuite, déplacer le calcul hors du fil principal quand il est vraiment lourd, grâce à un Web Worker — un fil d’exécution parallèle qui calcule sans bloquer l’interface.

Un dernier réflexe précieux : préférer les animations et transitions en CSS plutôt qu’en JavaScript. Une transition CSS s’exécute sur un fil de composition séparé et ne pèse pas sur le fil principal, donc n’allonge pas l’INP. Animer une apparition de carte en CSS plutôt qu’en boucle JavaScript, c’est de la réactivité gagnée gratuitement.

Au fond, l’INP et le poids du JavaScript sont les deux faces d’une même pièce : moins de code à exécuter, c’est un fil principal plus libre. C’est précisément l’objet du tutoriel Optimiser le JavaScript et le bundling, à suivre juste après celui-ci.

Point d’étape — Votre filtre manipule des classes CSS plutôt que de reconstruire le DOM, et les animations sont en CSS. L’INP au 75ᵉ centile devrait viser le vert.

Étape 7 — Vérification finale

Rejouez l’interaction dans l’onglet Performance : la tâche déclenchée par le clic ne doit plus dépasser 50 millisecondes, et l’INP rapporté pour cette interaction doit tomber sous 200 millisecondes. Si vous avez du trafic, surveillez le 75ᵉ centile de l’INP sur le terrain via PageSpeed Insights et la Search Console dans les semaines qui suivent — l’INP terrain met un peu de temps à refléter vos corrections, le temps que de nouvelles visites s’accumulent.

Le filtre de Niani répond désormais à l’instant : le bouton réagit immédiatement, la grille se met à jour dans la foulée, et plus personne ne clique deux fois par doute. Vous avez appliqué la même discipline que pour le LCP — mesurer, décomposer, corriger — sur une métrique d’interactivité.

🐞 Pièges fréquents

Symptôme Cause probable Correctif
INP qui reste à zéro dans les outils Aucune interaction n’a encore eu lieu L’INP se calcule après un vrai clic, tap ou frappe ; interagir puis re-mesurer
Le clic réagit, mais une frappe au clavier est lente Un autre gestionnaire (recherche en direct) bloque le fil à chaque touche Débattre (debounce) le traitement et céder le fil dans le gestionnaire de saisie
scheduler.yield is not a function Navigateur sans l’API moderne Utiliser le repli setTimeout(resolve, 0) comme dans l’exemple
INP bon en laboratoire, mauvais sur le terrain Vos visiteurs ont des appareils plus lents que votre poste Tester avec le bridage « 4× CPU » ; réduire réellement le travail, pas seulement le masquer

🌍 Réalités du terrain

L’INP est la métrique la plus sensible à la puissance de l’appareil, et c’est là que l’écart se creuse pour un public ouest-africain. Un téléphone d’entrée de gamme courant à Lomé ou Niamey a un processeur plusieurs fois plus lent que votre machine de développement : un traitement qui prend 80 millisecondes chez vous peut en prendre 350 chez votre client. Le bridage « 4× CPU » de l’onglet Performance simule justement ce genre d’appareil — prenez l’habitude de l’activer.

La conséquence pratique est simple : sur ces terminaux, céder le fil et alléger le JavaScript ne sont pas du luxe, ce sont les seuls leviers qui rendent l’interface utilisable. Une page qui répond instantanément sur un appareil modeste donne une impression de qualité bien supérieure à une page riche mais figée.

✅ Récapitulatif

Vous savez désormais traiter l’INP de façon structurée : comprendre qu’il mesure la latence des interactions, le décomposer en délai d’entrée, traitement et rendu, isoler l’interaction coupable, puis la corriger — retour visuel immédiat, cession du fil avec scheduler.yield(), et réduction du travail de fond. La réactivité d’une page se gagne en libérant le fil principal, exactement ce que le tutoriel sur le JavaScript pousse plus loin.

🧾 Aide-mémoire

Élément Rôle
Délai d’entrée · traitement · rendu Les trois phases d’une interaction
scheduler.yield() Rendre la main au navigateur entre deux morceaux de travail
Tâche > 50 ms Tâche « longue » à découper
Retour visuel d’abord, travail lourd ensuite Donner une réponse immédiate au geste
Classes CSS plutôt que reconstruction du DOM Réduire le coût de l’interaction
web-vitals/attribution Identifier l’interaction et la phase coupables
Cible INP ≤ 200 ms au 75ᵉ centile

💪 À vous de jouer

Prenez une interaction de votre page (filtre, ouverture de menu, onglet) et ajoutez un retour visuel immédiat suivi d’une cession du fil avant le travail lourd. Mesurez l’INP de l’interaction avant et après dans l’onglet Performance.

Voir une piste de solution

Sur un filtre non optimisé qui reconstruit le DOM, on observe souvent une tâche unique de 300 à 450 millisecondes. En activant le bouton puis en cédant le fil avant de filtrer, la tâche qui répond au clic tombe sous 50 millisecondes et l’INP rapporté passe au vert. En remplaçant en plus la reconstruction par un basculement de classes CSS, même le travail de fond cesse de bloquer.

Dans la même série

Pour aller plus loin

FAQ

Le défilement compte-t-il dans l’INP ?
Non. L’INP ne mesure que les clics, les appuis tactiles et les frappes au clavier. Le défilement, le survol et le zoom sont exclus — ils sont gérés différemment par le navigateur.

Pourquoi mon INP terrain ne s’améliore pas tout de suite après ma correction ?
Les données de terrain sont agrégées sur 28 jours glissants. Il faut laisser le temps à de nouvelles visites de remplacer les anciennes pour que le 75ᵉ centile reflète vos changements. Le laboratoire, lui, montre l’effet immédiatement.

Céder le fil ne rend-il pas l’opération plus lente au total ?
Légèrement, en temps total — mais ce n’est pas ce que ressent l’utilisateur. Ce qui compte pour l’INP, c’est la rapidité de la première réponse visible au geste. Une opération un peu plus longue mais qui répond tout de suite est perçue comme bien plus rapide qu’un bloc figé.

Partager