← Retour au blog
EngineeringBuild in PublicSEO

Nos pages Next.js etaient trop lourdes pour Bing. Le payload RSC faisait 58 % du HTML

Trois pages Next.js bloquees sur decouverte mais pas analysee dans Bing. L App Router envoie chaque octet deux fois. Nous en avons allege une de 48 Ko, Bing l a indexee.

8 octobre 2026

Nos pages Next.js etaient trop lourdes pour Bing. Le payload RSC faisait 58 % du HTML

Katto est un outil de clipping vidéo par IA. Vous lui donnez une vidéo longue, il y trouve les meilleurs moments et les découpe en clips verticaux sous-titrés. Je le construis seul, en public, avec Next.js et l'App Router. Voici une mesure qui a changé ma façon d'écrire des pages, un chiffre que j'ai annoncé faux devant tout le monde, et une hypothèse à moi qui valait 108 octets.

Le symptôme

Bing Webmaster Tools affichait plusieurs de nos pages comme découvertes mais jamais analysées. Ni bloquées par robots.txt, ni en noindex, ni lentes, ni redirigées. Découvertes, puis abandonnées. Google les indexait. Notre sitemap les listait. Elles répondaient 200 à qui les demandait.

Les pages refusées avaient un point commun, et ce n'était pas leur contenu.

La limite, et à quel point nous la connaissons mal

Microsoft ne publie aucune taille à partir de laquelle son robot abandonne. Ce que nous avons, ce sont nos propres pages, chacune avec son statut lu dans Bing Webmaster Tools et son poids servi mesuré le même jour, sans compression, avec l'agent de bingbot :

Une seule page de l'autre côté d'une ligne ne fait pas une loi. Mais il y a dans cette liste une observation qui vaut plus que tout le reste, parce que c'est deux fois la même URL. /compare/ai-video-clippers servait 172 295 octets et restait bloquée sur « découverte mais pas analysée » pendant des semaines. Nous l'avons réduite à environ 124 Ko. Elle est indexée aujourd'hui, à 127 951 octets. Même URL, même gabarit, même type de contenu : trop grosse, puis assez petite.

Il y a une semaine je vous aurais donné un autre chiffre, et il aurait été faux. J'avais trois relevés de statut et un trou bien visible dans la distribution de nos tailles, et j'y ai lu une limite vers 125 Ko. Deux de ces pages ont depuis été indexées à un peu moins de 128 Ko. Ce que notre site peut réellement dire, c'est que la ligne se situe entre 128 et 140 Ko, et que son autre côté fait toujours exactement une page de large.

Si vous êtes venu chercher un chiffre, la réponse honnête est de mesurer le vôtre. L'affirmation que je peux défendre est plus étroite et plus utile de toute façon : une page qui stagne sur « découverte » mérite d'être pesée avant d'être réécrite, et une page qui se met à être explorée après avoir maigri est la seule preuve qui compte vraiment.

Peser le fichier source ne vous apprend rien

Voici ce qui rend l'affaire propre aux React Server Components. L'App Router sérialise l'arbre rendu dans le même document HTML, sous forme d'une série de balises self.__next_f.push(...), pour qu'une navigation côté client puisse reprendre sans aller-retour supplémentaire. Ce payload n'est pas une requête séparée qu'un robot peut ignorer. Il est dans le document.

Mesuré sur une de nos pages : 125 981 octets servis, dont 73 571 pour ce payload. Cinquante-huit pour cent du document sont la copie sérialisée de ce que les quarante-deux pour cent restants disent déjà.

Donc une classe utilitaire écrite une seule fois dans une boucle de 24 lignes n'est pas envoyée 24 fois. Elle est envoyée 48 fois.

Ce que j'ai mis le plus longtemps à voir

Ça ne double pas la page. Ça double chaque représentation que la page porte déjà.

Notre roadmap rendait délibérément chaque entrée deux fois : une fois en texte visible, une fois dans un bloc ItemList de JSON-LD, pour que les lecteurs machines obtiennent le nom, la date et le statut comme des données plutôt que comme de la prose à interpréter. Raisonnable. Puis j'ai compté les occurrences de la description d'une seule entrée dans le document servi, et j'en ai trouvé quatre.

Deux représentations, chacune doublée. Quatre copies de la même phrase, et je n'avais jamais pensé à compter.

Le correctif n'était pas la compression. Le JSON-LD portait une description qui recopiait mot pour mot un texte que le lecteur voit trente lignes plus bas, dans la même langue. Elle n'apportait rien et coûtait deux des quatre copies. Retirée, le bloc garde le nom, la date et le statut, c'est-à-dire ce que la prose ne dit pas explicitement à une machine. Cette page est passée de 121 196 à 115 350 octets sans qu'aucune information n'en sorte. Les deux chiffres viennent du même build, avant et après ce seul changement, qui est la seule façon pour qu'un avant/après veuille dire quelque chose.

Si vous ne retenez qu'une habitude : avant de chercher à alléger quoi que ce soit, comptez combien de fois une phrase de votre contenu apparaît dans le document servi. Elle répond à une question à laquelle peser les sections ne répond pas.

Les deux leviers plus lourds

Les attributs de classe répétés vers des règles de feuille de style. Les utilitaires Tailwind sont merveilleusement bon marché à écrire, et ils partent sur chaque élément, deux fois. Déplacer les plus répétés dans des règles @apply a fait passer notre index des prix de 172 295 octets à environ 142 000. Ils représentaient à peu près 51 pour cent du balisage.

Les blocs non interactifs hors de l'arbre sérialisé. Pour être précis, parce que ça compte : ce sont des Server Components, donc ils ne sont jamais hydratés côté client. Le coût n'est pas l'hydratation, c'est la sérialisation. Chaque élément, chaque prop et chaque clé est écrit dans le flux pour qu'une navigation côté client puisse reconstruire l'arbre sans aller-retour. Un tableau statique sans aucun comportement client paie quand même ce prix, et quand la contrainte est le poids du document, il n'y a aucune raison qu'il reste des éléments React. Nous construisons désormais ces blocs comme une chaîne HTML à partir des mêmes données, posée en dangerouslySetInnerHTML, en échappant chaque valeur interpolée à la construction pour que l'échappement ne dépende pas de la provenance de la donnée. Encore 18 Ko sur l'index des prix. Une liste de 54 lignes sur la roadmap est passée de 157 308 à 119 031 de la même façon.

Ce qui n'a pas marché

Avant tout ça, j'étais certain que le problème venait des frontières de composants. Cinquante-quatre composants next/link dans une liste, chacun ajoutant son nom sérialisé, ses props et ses clés au payload. J'ai remplacé les 54 par de simples ancres.

Gain : 108 octets sur 157 200.

Link est bon marché. Le contenu qu'il enveloppe ne l'est pas. J'aurais passé une journée à réécrire des composants sur cette théorie si je n'avais pas mesuré avant et après, et ce résultat négatif a été le chiffre le plus utile de la journée.

Il y a un plancher, et il n'est pas bas

Notre page réelle la plus légère, /contact, sert 46 111 octets alors qu'elle ne porte qu'un titre, un formulaire court et quelques questions. Le reste, c'est la navigation, le pied de page, l'analytique et les polices. En dessous de ce chiffre il n'y a plus rien à gagner page par page : ce serait un chantier de coque, pas un chantier de page. Connaître le plancher vous dit quand vous arrêter.

Une traduction coûte des octets, pas des mots

Nous avons mis cette roadmap en dix langues. Même balisage, même structure, même information. La page anglaise sert 116 239 octets, la française 119 838, la japonaise 122 155 et la hindi 135 379.

Le devanagari prend trois octets par caractère en UTF-8 là où le texte latin en prend un, et le payload le paie une seconde fois. Aucune technique au niveau de la page n'y touche : même balisage, même information, dix-neuf kilo-octets d'écart.

La page hindi est la plus intéressante, parce qu'à 135 379 octets elle tombe en plein dans la bande que nous ne savons pas trancher. Savoir si Bing la traite est l'expérience la moins chère dont nous disposons, et elle coûte une inspection d'URL.

J'avais écrit ici un paragraphe soutenant que ce dépassement n'avait pas beaucoup d'importance, puisqu'il tombe à la fin du document et que la fin de cette page est une liste dont les titres restent en anglais de toute façon. Je l'ai coupé, parce qu'il suppose que le robot lit jusqu'à épuiser son budget puis s'arrête. Tout ce que j'ai réellement observé dit seulement que les pages au-dessus d'une certaine taille ne sont pas traitées. Si le document est rejeté en entier, son ordre n'achète rien, et je n'ai de preuve ni dans un sens ni dans l'autre. C'est une théorie confortable sur ma propre page, et c'est le genre qu'il faut supprimer en priorité.

C'est un budget, pas un correctif

L'index des prix que j'avais fait passer de 172 295 à 124 202 octets est à 127 951 aujourd'hui, parce que nous avons continué à y ajouter : plus d'outils, plus de colonnes, dix traductions. Il reste indexé, et c'est tout l'intérêt, mais il est aussi revenu à quelques kilo-octets de la ligne. Rien ne s'est mal passé. Nous avons simplement redépensé le budget sans le surveiller.

C'est ça, la vraie conclusion. Dans un framework qui sérialise sa propre sortie, le poids d'une page n'est pas un nettoyage qu'on fait une fois. C'est un nombre qui monte à chaque fois que quelqu'un fait du bon travail, et la seule défense est de mesurer le document servi plutôt que la source, un changement à la fois.

Comment mesurer les vôtres

Articles liés

Prêt à transformer vos vidéos en clips viraux ?

Katto découpe, sous-titre et recadre automatiquement vos vidéos longues en contenu court.

Essayer Katto gratuitement →
Nos pages Next.js etaient trop lourdes pour Bing. Le payload RSC faisait 58 % du HTML | Katto