Votre LCP affiche 2,3 s en bas de Search Console. Vert. Vous respirez. Sauf que votre formulaire de devis, lui, met 4,1 s à se rendre, et c'est la seule zone qui convertit. Le LCP ne vous l'a jamais dit, parce que le LCP ne regarde qu'un seul élément, celui que le navigateur a jugé le plus gros au-dessus du pli. La plupart du temps votre hero image. Tout le reste de la page existe dans un trou noir métrique. C'est précisément ce que la Container Timing API vient combler.
Chrome 147, sorti en stable le 7 avril 2026, a embarqué l'implémentation derrière le flag chrome://flags/#enable-experimental-web-platform-features. Chrome 148, prévu pour la mi-mai 2026, ouvre l'origin trial public : vous enregistrez votre origine, vous récupérez un token, vous le collez dans votre <head>, et la fonctionnalité s'active automatiquement pour tous vos visiteurs sans qu'ils touchent à un flag. L'OT court jusqu'à Chrome 152 d'après la note d'intention envoyée sur blink-dev par Igalia et Bloomberg, soit environ huit mois de fenêtre pour tester en production.
Pourquoi le LCP global ne suffit plus#
Le Largest Contentful Paint a été pensé en 2019 pour répondre à une question simple : à quel moment l'utilisateur voit-il quelque chose de gros sur la page ? Réponse opérationnelle pour 2019. Insuffisante pour 2026, où une page e-commerce moderne empile un hero, une grille produits, des reviews, un sticky add-to-cart, des recommandations cross-sell, un footer dense. Chacun de ces blocs charge ses propres ressources critiques avec son propre profil de latence. Le LCP en mesure un seul.
Element Timing, exposé en stable depuis Chrome 77, autorise déjà le marquage d'éléments individuels avec l'attribut elementtiming. Le problème : il faut taguer chaque élément un par un. Pour un widget composé d'une trentaine de nœuds DOM, vous instrumentez trente fois, vous récupérez trente entries, vous les agrégez côté JS. Surcoût d'instrumentation, fragilité au moindre refactor, et résultat qui ne dit toujours pas "le widget est fini de peindre".
Container Timing prend le problème à l'envers. Vous taguez le conteneur racine d'un bloc, le navigateur agrège pour vous tous les paints qui se produisent dans son sous-arbre, et vous récupérez une entry unique quand le bloc considère qu'il a fini sa première peinture significative. Sous le capot, Blink réutilise les détecteurs ImagePaintTimingDetector et TextPaintTimingDetector qui alimentent déjà LCP et Element Timing. Pas une nouvelle pipeline, juste un mode d'agrégation différent sur le même signal.
La timeline Chromium en clair#
Pour bien situer où on en est, le changelog public est plus parlant qu'un communiqué.
- Décembre 2024 : explainer initial publié sur le repo WICG. Bloomberg Engineering pousse le sujet, Igalia prend l'implémentation côté Blink. Un polyfill JavaScript basé sur MutationObserver est livré pour expérimenter, déjà marqué comme inefficace en production.
- Chrome 144 (janvier 2026) : feature flag interne
ContainerTimingactivable via--enable-blink-features=ContainerTimingen ligne de commande. Réservé aux devs Chromium et aux curieux qui compilent leurs propres builds. - Chrome 145 (février 2026) : flag accessible via
chrome://flags/#enable-experimental-web-platform-features. Premier test grand public possible dans Canary et Beta. - Chrome 147 (7 avril 2026) : flag promu en stable. Vous l'activez dans Chrome standard, vous testez sur votre propre machine, mais vos visiteurs ne l'ont pas par défaut.
- Chrome 148 (mai 2026) : ouverture de l'origin trial. Token à enregistrer sur la console origin trials de Chrome, balise
<meta http-equiv="origin-trial" content="...">dans le<head>, et la fonctionnalité s'active sur toutes les sessions de vos visiteurs Chromium ≥ 148. - Chrome 152 (octobre 2026 selon le rythme stable de six semaines) : fin de l'OT. Trois issues possibles. Shipping en stable si la spec satisfait WICG. Prolongation par un OT additionnel si des ajustements API restent à faire. Abandon, peu probable vu l'investissement de Bloomberg sur le sujet.
Quatre versions entre le flag commande et l'OT public, c'est rapide pour un nouveau signal de perf. La spec a bénéficié des leçons des trois années chaotiques de Soft Navigations, dont j'ai détaillé la saga dans Origin trial Chrome 147 : vos SPA vont enfin compter en INP.
Le code minimal qui marche#
Côté HTML, vous ajoutez l'attribut containertiming sur le conteneur racine de la zone que vous voulez mesurer. La valeur de l'attribut sert d'identifiant lisible dans vos logs.
<section containertiming="hero-home">
<h1>Devis sous 24h</h1>
<picture>
<source srcset="/hero.avif" type="image/avif" />
<img src="/hero.jpg" alt="" width="1200" height="600" />
</picture>
<form action="/devis" method="post">
<input name="email" type="email" required />
<button type="submit">Demander un devis</button>
</form>
</section>
<aside containertiming="recommendations">
<h2>Produits recommandés</h2>
<div class="grid"><!-- ... 6 cartes produit injectées en JS ... --></div>
</aside>
Côté JavaScript, vous installez un PerformanceObserver classique avec type: "container" et buffered: true. Le buffered: true garantit que vous récupérez les entries émises avant que votre observer ne soit installé, ce qui est critique vu que vous voulez probablement instrumenter dans un script chargé après le hero.
if (PerformanceObserver.supportedEntryTypes.includes('container')) {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log('[container]', {
id: entry.identifier,
firstRender: entry.firstRenderTime,
latestPaint: entry.startTime,
size: entry.size,
lastEl: entry.lastPaintedElement?.tagName,
})
// Envoi RUM : Beacon vers votre endpoint analytics
navigator.sendBeacon(
'/rum',
JSON.stringify({
metric: 'container-timing',
id: entry.identifier,
first: entry.firstRenderTime,
latest: entry.startTime,
}),
)
}
})
observer.observe({ type: 'container', buffered: true })
}
Détection de support en deux temps : PerformanceObserver.supportedEntryTypes.includes('container') côté observer, ou typeof PerformanceContainerTiming !== 'undefined' côté global, l'un ou l'autre fait l'affaire. Les navigateurs qui ne connaissent pas l'API ignorent silencieusement l'attribut containertiming. Aucun risque de casser une page existante en l'ajoutant.
Les propriétés de l'entry, sans approximation#
Une PerformanceContainerTiming expose six champs utiles, plus les deux hérités de PerformanceEntry.
identifier: la valeur de l'attributcontainertiming, telle quelle. C'est votre clé d'agrégation côté RUM.firstRenderTime: timestamp du tout premier paint contentful dans ce conteneur. Le signal le plus proche d'un LCP local, à utiliser comme métrique principale.startTime: timestamp du paint le plus récent dans le conteneur. Mis à jour à chaque nouvelle entry émise. Utile pour suivre les conteneurs qui se remplissent en plusieurs vagues, typiquement une grille de produits chargée par lots.size: aire totale peinte dans le conteneur, en pixels CSS au carré. Pratique pour pondérer l'importance d'une entry quand un conteneur a beaucoup d'éléments.intersectionRect: bounding box accumulée de tous les paints du conteneur, enDOMRectReadOnly. Permet de savoir où dans le viewport le contenu apparaît.lastPaintedElement: référence à l'élément DOM peint le plus récemment. Peut être null avant le premier paint. Utile pour debugger ce qui a déclenché la dernière entry.duration: fixé à 0. Ne pas chercher d'interprétation, c'est un champ hérité dePerformanceEntryqui n'a pas de sens ici.name: identique àidentifier. Pour la compat avec les outils qui filtrent parname.
Point important sur le comportement des entries : un conteneur ne génère pas une entry par paint. Le navigateur regroupe les paints et n'émet une nouvelle entry que quand l'aire peinte augmente, c'est-à-dire quand du nouveau contenu apparaît. Un re-paint d'une zone déjà mesurée ne génère rien. Si votre conteneur charge progressivement six cartes produit, vous verrez six entries successives, chacune avec un size plus grand que la précédente, et startTime qui avance à chaque émission.
L'exclusion ciblée avec containertiming-ignore#
Quand vous taguez un gros conteneur, vous embarquez tout son sous-arbre. Sauf que tout n'est pas pertinent. Les bannières d'ads tierces, par exemple, polluent vos mesures et bloquent souvent le rendu pendant des centaines de millisecondes. La spec prévoit containertiming-ignore, un attribut booléen qui exclut un sous-arbre du tracking du parent.
<main containertiming="article-body">
<h1>Titre de l'article</h1>
<p>Lead paragraphe...</p>
<div containertiming-ignore class="ad-slot">
<!-- bannière Adsense ignorée -->
</div>
<p>Suite de l'article...</p>
</main>
L'avantage est double : vous nettoyez votre mesure des éléments qui dépendent de tiers que vous ne contrôlez pas, et vous réduisez la traversée d'arbre côté Blink. L'implémentation Igalia propage un flag SelfOrAncestorHasContainerTiming sur chaque nœud descendant d'un conteneur tagué. Plus l'arbre est profond, plus la traversée coûte. containertiming-ignore coupe la propagation. Sur un footer plein d'icônes de paiement injectées par un partenaire, le gain de perf de l'instrumentation elle-même est mesurable.
Le nom de l'attribut peut encore changer. Le blog Chrome cite explicitement que containertiming-ignore "might change based on feedback received". À surveiller pendant l'OT, et à isoler dans une fonction utilitaire pour pouvoir renommer en un endroit le jour où la spec tranche.
Use cases qui méritent vraiment l'instrumentation#
Tagger tout votre site en containertiming est contre-productif. Vous générez du bruit, vous payez le coût de traversée Blink sur des zones sans enjeu, et votre RUM est inondé d'entries inutiles. Ciblez les zones qui ont un impact business mesurable.
Le hero qui n'est pas le LCP officiel. Sur une landing avec une vidéo de fond ou un background SVG complexe, le LCP désigne souvent la vidéo (qui se charge tard) plutôt que le H1 visible immédiatement. Vous voulez savoir quand le visuel "perçu" est prêt, pas quand le navigateur a fini de buffer 30 Mo de MP4. Tagguer le bloc hero avec containertiming vous donne le timing du premier paint significatif, qui correspond souvent à ce que l'utilisateur considère comme "la page est là".
Les sections produit critiques. Sur une fiche produit e-commerce, le module prix + bouton d'achat est la zone qui convertit. Si elle se peint en 1,2 s alors que le hero met 0,8 s, vous avez un signal clair que c'est cette zone qu'il faut prioriser, pas l'image principale. Le LCP ne vous le dira pas, parce que le LCP a choisi l'image.
Les formulaires de génération de leads. Un devis B2B, un calendrier de RDV intégré, un module de simulation de prix. Souvent injectés en JavaScript après le DOMContentLoaded, ils mettent plusieurs secondes à devenir interactifs. containertiming vous donne un timing fiable du moment où le formulaire est visuellement complet, ce qui se croise très bien avec les events submit de votre tracking conversion.
Les recommandations cross-sell. Une grille de produits recommandés en bas de fiche, alimentée par un appel API à votre moteur de reco. Souvent en lazy-load, souvent sous le pli, mais consultée par les utilisateurs qui scrollent. Le timing de chargement de cette zone n'apparaît dans aucune métrique Core Web Vitals standard, mais il influence directement votre revenu par session.
Les widgets carte / map / planning. Quand vous embarquez Leaflet, Mapbox, Google Maps, FullCalendar, le module met plusieurs centaines de millisecondes à initialiser. Si votre business model dépend du widget (location de logement, prise de RDV), mesurez-le précisément avec un conteneur dédié.
Côté SEO pur, le bénéfice indirect mais réel : vous identifiez les sections que Google ne voit pas dans son LCP, mais qui pèsent sur le comportement utilisateur. Bounce rate, scroll depth, conversion. Un site qui optimise son LCP, CLS et INP en 2026 avec le seul œil de CrUX optimise une métrique. Un site qui instrumente avec Container Timing voit où l'utilisateur réel décroche.
Et CrUX dans tout ça#
Question qui revient à chaque nouvel OT perf : est-ce que Google va consommer cette donnée dans CrUX et l'utiliser comme signal de classement ? Réponse honnête : non, pas tout de suite, et probablement pas avant 2027.
Le pattern Chrome est constant. Une API perf est livrée, elle reste en OT plusieurs mois, elle ship en stable, puis seulement plus tard CrUX commence à collecter, et plus tard encore Search Console intègre. Pour Soft Navigations, on attend toujours après quatre ans d'itérations. Pour Container Timing, le blog Chrome ne mentionne aucun engagement d'intégration CrUX. C'est explicitement une API de monitoring custom, pas un futur signal de classement.
Ce qui ne veut pas dire que ça n'aura aucun impact SEO. Indirectement, deux effets jouent. Premier, vous identifiez et corrigez des goulots d'étranglement qui pèsent sur l'engagement utilisateur, ce qui remonte mécaniquement dans les signaux d'expérience que Google capte autrement (taux de rebond, dwell time, retours rapides aux résultats). Second, à terme, certaines de ces métriques peuvent influencer le LCP global lui-même : un hero plus rapide à se peindre, c'est un meilleur LCP CrUX, point. Vous gagnez sur les deux tableaux, juste pas immédiatement et pas par la voie directe.
Fallback et stratégie de déploiement progressive#
Le pire scénario, c'est de remplacer votre stack RUM actuelle par Container Timing alors que 70 % de vos visiteurs sont sur Safari iOS, Firefox ou un vieux Chrome. Vous perdez de la donnée, vous biaisez vos analyses. La bonne approche est additive.
// Métriques de base, supportées partout depuis 2020+
import { onCLS, onINP, onLCP } from 'web-vitals'
onLCP((metric) => sendBeacon('lcp', metric))
onCLS((metric) => sendBeacon('cls', metric))
onINP((metric) => sendBeacon('inp', metric))
// Couche additionnelle : Container Timing si dispo
if (PerformanceObserver.supportedEntryTypes.includes('container')) {
const obs = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
sendBeacon('container', {
id: entry.identifier,
first: entry.firstRenderTime,
size: entry.size,
})
}
})
obs.observe({ type: 'container', buffered: true })
}
Le polyfill Bloomberg, historiquement utile pour défricher l'API, est maintenant déprécié dans le README officiel. L'équipe Igalia le dit sans détour : il "would need to run in the head, block rendering, and scan the DOM for a containertiming attribute and modify the children elements to add the elementtiming attributes, then setup a MutationObserver to watch for changes in the tree". Le coût en rendering blocking annule le bénéfice métrique. Si vous voulez tester avant l'OT public, restez sur Chrome Canary ou Beta avec le flag, n'essayez pas de polyfiller en JS pour Safari.
Pour Firefox et Safari spécifiquement, aucune des deux équipes n'a pris de position publique sur Container Timing à date. WebKit n'a pas répondu au standards position request, Mozilla non plus. C'est cohérent avec leur historique sur les APIs perf Chrome-only (Element Timing n'a jamais été implémenté hors Chromium non plus). Vous instrumentez pour Chromium, vous avez 65 % à 70 % de votre trafic selon Statcounter, et le reste reste sur des Core Web Vitals classiques. Statistiquement suffisant pour piloter des décisions perf.
Mon verdict de dev#
Pour ceux qui veulent du concret : activez le flag dès Chrome 147 sur votre poste, ouvrez votre site, ajoutez containertiming sur trois ou quatre zones critiques, mettez en place le PerformanceObserver, regardez les timings dans la console. Vous allez probablement découvrir qu'une zone que vous croyiez rapide met en réalité 1,5 s à se peindre, parce qu'une dépendance JavaScript que vous aviez oubliée bloque le rendu. Sans Container Timing, vous ne le voyez pas. Avec, c'est évident en cinq minutes.
Pour la production : attendez l'OT Chrome 148 et déployez un token. C'est gratuit, c'est sans engagement, et vous récupérez de la donnée terrain sur vos vrais utilisateurs Chrome. Le seul effort est de monter votre pipeline RUM pour ingérer les entries container à côté de vos web-vitals classiques. Comptez deux à trois jours d'un dev pour brancher proprement, dédupliquer, et alimenter votre dashboard.
Pour les SEO pragmatiques : pas la peine de paniquer si vous ne le faites pas tout de suite. Tant que CrUX ne consomme pas, votre ranking ne dépend pas de cette API. C'est un outil d'observabilité avancée, pas un nouveau signal de classement. Priorisez d'abord les chantiers qui bougent le LCP et l'INP officiels via les recommandations de vitesse de chargement et impact SEO. Container Timing arrivera dans votre stack au second semestre 2026, quand l'OT mûrira et que les agences commenceront à le proposer en standard dans leurs audits perf.
Une dernière nuance honnête : je ne sais pas combien de temps l'OT va vraiment durer. Les huit mois annoncés peuvent se prolonger si Bloomberg et Igalia rencontrent des résistances côté WICG, notamment sur les approches alternatives discutées dans container-definitions.md (registration dynamique via JS, ou <script type="containerrules">). Si la spec change pendant l'OT, vous devrez adapter votre instrumentation. Restez vigilants sur le blog Chrome et la liste blink-dev, pas plus.
À vous de jouer.
Sources#
- Chrome for Developers, Container Timing Origin Trial (mai 2026)
- Chrome for Developers, Chrome 147 beta release notes
- WICG Container Timing spec et explainer
- Igalia, Container Timing: measuring web components performance (10 février 2026)
- Igalia, The implementation of Container Timing: aggregating paints in Blink (26 mars 2026)
- Bloomberg container-timing repository (polyfill historique)
- Intent to Experiment: Container Timing (blink-dev)
- WICG container-timing specification draft
- Container definitions document (WICG)
- web.dev, Element Timing API reference





