React 19 : une modification de la façon dont Suspense gère les requêtes en parallèle,
pourrait potentiellement dégrader de manière significative la performance de nombreux sites web qui s'appuient sur React

React 19 a introduit plusieurs nouvelles fonctionnalités et améliorations, mais certaines d’entre elles ont été accueillies avec scepticisme. Par exemple, la suppression d’une fonctionnalité liée à Suspense a entraîné des états de chargement instantanés, ce qui a provoqué un effet cascade lorsque plusieurs composants initiés par des requêtes de données se suspendent simultanément. Cette modification a été perçue par certains comme une régression qui pourrait affecter la performance et l’expérience utilisateur.

React, l'un des framework d'interface utilisateur les plus populaires et les plus utilisés, alimente certains grands noms du web comme Netflix, Airbnb, Discord et bien sûr, le berceau de React, Meta (Facebook, Instagram et Whatsapp). Étant donné que React est utilisé pour créer des interfaces utilisateur qui sont utilisées par des milliards de personnes, il est raisonnable de supposer qu'une part non négligeable de tout le trafic internet est « gérée » par React.

En début d'année, la très attendue React 19 a été annoncée, mais avec toutes les nouvelles fonctionnalités brillantes et les améliorations DX, il y avait un petit changement qui est passé inaperçu jusqu'à la semaine dernière et qui pourrait potentiellement dégrader de manière significative la performance de nombreux sites web qui s'appuient sur React.

Tout a commencé par ce post sur X de Dominik, alias TkDodo, l'un des principaux mainteneurs de TanStack Query :

Est-ce que je me fais des idées ou est-ce qu'il y a une différence entre React 18 et 19 en ce qui concerne la façon dont Suspense gère le parallel fetch ? Dans 18, il y a une division "par composant", donc mettre deux composants dans la même frontière de Suspense, où chacun faisait un fetch, les lançait toujours en parallèle :

Code React : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
<Suspense fallback={<p>...</p>}>
  <RepoData name="tanstack/query" />
  <RepoData name="tanstack/table" />
</Suspense>

Ceci lance deux requêtes en parallèle, attend que les deux soient résolues et affiche ensuite le sous-arbre entier.

Dans React 19, pour autant que je sache, les requêtes sont maintenant exécutées en cascade. Je crois me souvenir que @rickhanlonii avait mentionné quelque chose de ce genre, mais je n'ai pas trouvé de preuve maintenant.


Pour mémoire, React Suspense est une fonctionnalité introduite dans React 16.6 qui permet de « suspendre » le rendu de l’arbre de composants jusqu’à ce qu’une certaine condition soit remplie, généralement utilisée pour gérer la récupération de données de manière déclarative. Elle améliore l’expérience utilisateur en évitant l’affichage de contenu incomplet ou vide en attendant que les données soient chargées. Suspense utilise une approche déclarative pour la gestion des opérations asynchrones comme la récupération de données ou le fractionnement de code, et permet d’afficher un contenu de repli ou des indications de chargement pendant que les données sont en cours de chargement. C’est une partie importante de la construction d’applications React plus réactives et efficaces.

Les propos de TkDodo ont entraîné des réactions comme celle d'Adam Rackis :

C'est un changement exaspérant et incompréhensible. D'après les commentaires, il _semble_ que cela s'applique aux composants clients, mais les fetches parallèles fonctionnent toujours dans RSC. Ce qui détruit react-query, la seule bonne façon de gérer les données avec React. J'espère que les esprits s'apaiseront, mais je suis sûr que ce ne sera pas le cas.
Le pire dans tout cela, c'est que bien qu'il s'agisse d'un changement radical en termes de performances qui va affecter de nombreuses personnes qui dépendent de ce modèle, il n'y a qu'une seule ligne dans une liste à puces qui mentionne sans cérémonie ce changement :

Nom : changement.png
Affichages : 9097
Taille : 9,8 Ko

Récapitulatif de Suspense

Pour comprendre de quoi il s'agit, il faut d'abord faire un petit récapitulatif sur Suspense de React. Suspense est un composant React qui vous permet d'afficher un fallback jusqu'à ce que ses enfants aient fini de se charger, soit parce que ces composants enfants sont chargés paresseusement, soit parce qu'ils utilisent un mécanisme de récupération de données activé par Suspense.

Il s'utilise de la manière suivante :

Code React : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
<Suspense fallback={<Loading />}>
  <ComponentThatFetchesDataOrIsLazyLoaded />
</Suspense>

Bien que Suspense fasse partie de l'API de React depuis un certain temps maintenant, pendant longtemps, la seule utilisation officiellement approuvée était de charger paresseusement des composants avec React.lazy, ce qui est extrêmement utile pour diviser le code de votre application et ne charger que les parties divisées lorsque c'est nécessaire.

Lorsqu'il est utilisé avec React.lazy, en essayant de rendre le composant chargé paresseusement pour la première fois (c'est-à-dire avant de le charger paresseusement), il déclenche la limite de Suspense (c'est-à-dire le Suspense enveloppant le composant) et rend le fallback jusqu'à ce que la récupération du code du composant soit terminée, puis il rend le composant lui-même.

Pendant longtemps, les développeurs ont attendu un support officiel de Suspense pour la récupération de données sur le client (cela fonctionne déjà sur le serveur en utilisant les RSC), mais ils ont du attendre longtemps et, malgré cela, beaucoup de bibliothèques (TanStack Query étant l'une d'entre elles) l'ont implémenté en explorant les internes de React. Pour cette raison, il y a beaucoup d'applications en production qui utilisent actuellement Suspense pour la récupération de données sur le client.

Comprendre le changement

En clair, React 19 désactive le rendu parallèle des frères à l'intérieur de la même frontière Suspense, ce qui introduit essentiellement des chutes de données pour les données qui sont récupérées à l'intérieur de ces frères.

Voici un exemple qui illustre cette idée :

Code React : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function App() {
  return (
    <>
      <Suspense fallback={"Loading..."}>
        <ComponentThatFetchesData val={1} />
        <ComponentThatFetchesData val={2} />
        <ComponentThatFetchesData val={3} />
      </Suspense>
    </>
  );
}

const ComponentThatFetchesData = ({ val }) => {
  const result = fetchSomethingSuspense(val);

  return <div>{result}</div>;
};

Demo : https://stackblitz.com/edit/vitejs-v...=src%2FApp.jsx

Dans cet exemple (dans React 18), même si fetchSomethingSuspense provoque la suspension du premier ComponentThatFetchesData, React essaiera toujours de rendre ses frères, ce qui déclenchera la collecte de données pour chacun d'entre eux en parallèle.

Cela peut être vu en regardant la console où sont enregistré le moment où chaque récupération de données a été déclenchée :

Nom : un.png
Affichages : 656
Taille : 4,1 Ko

Toutes les données sont récupérées presque au même moment.

Regardons maintenant ce qui se passe lorsque nous exécutons exactement le même code dans React 19 (canary) :

Nom : deux.png
Affichages : 674
Taille : 4,2 Ko

Demo: https://stackblitz.com/edit/vitejs-v...=src%2FApp.jsx

Lorsque nous regardons à nouveau la console, nous remarquons qu'il y a maintenant une différence, car chaque récupération de données ne démarre qu'après que la précédente ait été achevée.

Ceci est dû au PR suivant : https://github.com/facebook/react/pull/26380

Les conséquences

Heureusement, cette histoire se termine bien. Après de nombreuses réactions publiques, des discussions animées et probablement beaucoup de discussions en coulisses, l'équipe React a fait marche arrière et a décidé de suspendre ce changement pour l'instant.

bonnes nouvelles concernant Suspense, je viens de rencontrer @rickhanlonii @en_JS @acdlite
  • Nous nous intéressons beaucoup aux SPA, mais l'équipe a mal évalué le nombre de personnes qui s'en servent aujourd'hui.
  • Nous recommandons toujours le préchargement, mais nous reconnaissons que ce n'est pas toujours pratique.
  • Nous prévoyons de retarder la sortie de la version 19.0 jusqu'à ce que nous trouvions un bon correctif

Plus d'informations à venir
Conclusion

Ce n'est pas la première fois que la communauté s'oppose à des changements introduits dans React sans tenir compte de la façon dont React est utilisé en dehors de Meta et Vercel. Il est clair qu'il y a un décalage entre ce que les mainteneurs de React pensent être le meilleur pour l'avenir de React et les opinions de la communauté sur le sujet.

La controverse entourant React 19 souligne le défi constant auquel sont confrontés les développeurs : équilibrer l’adoption de nouvelles technologies avec la maintenance de la stabilité des applications existantes. Alors que React continue d’évoluer, il est essentiel que la communauté collabore pour surmonter ces défis et tirer le meilleur parti des avancées technologiques.

Source : Henrique Yuji

Et vous ?

Quel impact pensez-vous que React 19 aura sur l’expérience utilisateur des sites web à grande échelle ?
Comment les développeurs peuvent-ils s’assurer que les mises à jour des frameworks comme React n’affectent pas négativement la performance des sites web ?
Quelles mesures préventives peuvent être prises pour éviter les ralentissements potentiels dus aux mises à jour technologiques ?
En tant qu’utilisateur, avez-vous remarqué des changements dans la vitesse de chargement des sites web depuis la mise à jour React 19 ?
Quelle est l’importance de l’équilibre entre l’innovation technologique et la stabilité de performance dans le développement web ?