Next.js
Performance
Cache
Arquitetura
React

Mise en cache et streaming dans Next.js : les performances sont devenues une décision architecturale

La performance perçue n’est plus un ajustement final et est devenue un choix architectural, avec des gains réels et le risque des données anciennes.

Mise en cache et streaming dans Next.js : les performances sont devenues une décision architecturale

Pendant longtemps, la performance a été la dernière étape du projet. L'application a été construite, mesurée à la fin, et lorsque la page était lente, des palliatifs sortaient : un cache ici, un spinner là, un chargement paresseux là. Il s'agissait d'un détail de finition, traité après que les décisions importantes aient déjà été prises.

Modern Next.js démantèle cet ordre. Le cache, le streaming et le suspense ne sont pas des boutons que vous activez à la fin ; ce sont des propriétés de la façon dont la page est assemblée et livrée. Décider quand les données sont recalculées, dans quel ordre les parties de l'écran apparaissent et ce qui peut être servi avant le reste sont des choix structurels. La thèse de ce texte est simple : la performance perçue est devenue une décision architecturale, et la traiter comme un détail final est devenue coûteuse.

Trois mécanismes qui résolvent différents problèmes

Cela vaut la peine de séparer ce que fait chaque pièce, car elles ont tendance à être confondues avec une seule et vague idée de « partir vite ».

Le cache consiste à ne pas refaire le travail. Si des données ont déjà été récupérées ou qu'une page a déjà été rendue, la sauvegarder évite de payer à nouveau le même coût lors de la prochaine requête. Le gain réside dans le débit et la latence : les réponses qui n'ont pas besoin de toucher la banque sont émises en fractions de temps.

Le streaming consiste à ne pas attendre que tout soit prêt pour commencer à être diffusé. Au lieu de conserver la page entière jusqu'à ce que la partie la plus lente se termine, le serveur envoie le HTML par morceaux au fur et à mesure que chacun devient disponible. L'utilisateur commence à voir et à interagir avec ce qui est déjà arrivé pendant que le reste est encore en cours d'assemblage.

Le suspense est ce qui rend le streaming utilisable. Il permet de déclarer, dans le composant lui-même, "tandis que ces données ne suffisent pas, montrez ceci". Il marque les limites entre ce qui est prêt et ce qui est encore en cours de chargement, donnant au framework la permission d'envoyer l'écran en morceaux cohérents au lieu d'un seul bloc.

Comment ils travaillent ensemble

La magie apparaît dans la combinaison. Imaginez une page produit : en-tête, données sur les articles, avis et recommandations. Les données d'en-tête et d'article sont rapides. Les évaluations reposent sur une forte agrégation. Les recommandations appellent à un service externe lent.

Dans l'ancien modèle, la page entière attendait le composant le plus lent. L'utilisateur a regardé un écran vide jusqu'à ce que tout soit terminé, y compris la recommandation provenant d'un tiers grincheux. Les performances de la page étaient l'otage de son pire élément.

Avec Suspense délimitant chaque bloc et le streaming actif, le serveur fournit instantanément l'en-tête et les données de l'élément, avec des indicateurs de chargement à la place des évaluations et des recommandations. Au fur et à mesure que chaque pièce est prête sur le serveur, elle est transmise et s'enclenche. En dessous, le cache garantit que lors de la prochaine visite, les parties qui n'ont pas changé n'auront même pas besoin d'être recalculées. Les trois mécanismes s'additionnent : la mise en cache réduit le travail, le streaming supprime l'attente dans le pire des cas, Suspense organise la livraison.

Le résultat est que les performances perçues s'arrêtent en fonction du composant le plus lent et démarrent en fonction de la façon dont vous avez tracé les limites. Et tracer des frontières, c’est de l’architecture.

Pourquoi cela devient une décision architecturale et non un ajustement final

Notez que chaque choix ci-dessus a été fait au début et non à la fin. Où placer une limite de suspension, quelles données peuvent attendre et lesquelles doivent être dans le premier octet, ce qui peut être mis en cache et pendant combien de temps : tout cela façonne la structure des composants et la manière dont les données sont récupérées.

Vous ne pouvez pas « ajouter du streaming plus tard » sur une page qui a été écrite comme un bloc monolithique qui récupère tout en même temps. Pour diffuser, la page doit avoir été conçue en parties indépendantes, chacune avec sa propre limite de chargement. Cette décomposition est une décision de conception qui intervient au début, en même temps que la modélisation des données.

Il en va de même pour le cache. Décider de ce qui peut être servi à partir d'une version enregistrée et de ce qui doit être toujours à jour revient, en pratique, à classer les données de votre domaine par tolérance obsolète. Il ne s’agit pas d’un ajustement de performances, c’est d’une déclaration sur l’entreprise : ce prix pourrait-il dater de quelques minutes ? Cet équilibre, non ? Ces réponses appartiennent à l’architecture, et ceux qui les repoussent jusqu’au bout découvrent que réécrire la structure de recherche des données coûte bien plus cher que d’y avoir pensé auparavant. La relation avec le reste du framework est plus claire dans le guide Next.js App Router.

Le risque que personne n'évoque dans le beau slide : cache d'incompréhension

Le cache est la partie la plus séduisante et la plus dangereuse. L'expression classique selon laquelle l'invalidation du cache est l'un des problèmes difficiles de l'informatique n'est pas une blague de programmeur, c'est une description de production.

Le problème central concerne les anciennes données. Dès que vous décidez d’enregistrer une réponse, vous acceptez qu’elle puisse être obsolète lorsque quelqu’un la relit. Pour un contenu qui change lentement, parfait. Pour un solde, un stock, un état de commande, servir une version trop longtemps sauvegardée, c'est montrer à l'utilisateur une réalité qui n'existe plus. Et le pire type de ce bug est le bug silencieux : rien ne se casse, rien ne donne d'erreur, l'écran ment simplement.

Next.js propose des contrôles de mise en cache précis, précisément parce que ces décisions doivent être spécifiques aux données et non globales. Mais un contrôle précis est une arme à double tranchant : le développeur qui ne comprend pas exactement dans quelle couche les données sont stockées va déboguer les comportements fantômes. Pourquoi cette page n'est-elle pas mise à jour ? Parce qu'il existe une couche de cache dont il a oublié l'existence, avec une clé d'invalidation que personne n'a déclenchée. Quiconque souhaite approfondir trouvera un bon aperçu dans bonnes pratiques de mise en cache dans les applications.

La complexité fait partie du deal

Il y a un coût cognitif qui doit être honnêtement inclus. Avec plusieurs couches de limites de mise en cache, de streaming et de Suspense, le modèle mental de « ce qui se passe lorsque l'utilisateur demande cette page » devient plus riche et plus difficile à retenir dans votre tête.

Les données peuvent être stockées à plusieurs niveaux, avec des durées de vie différentes. Une partie de la page s'affiche sur le serveur et est diffusée, une autre s'hydrate sur le client. Lorsqu’un élément semble obsolète, l’enquête doit passer par ces différentes couches pour découvrir où l’ancienne version est restée bloquée. Cela nécessite que l'équipe comprenne le modèle, et ne se contente pas de copier les configurations à partir d'un exemple sur Internet.

Par conséquent, la recommandation pratique est d’être explicite et prudente. Commencez avec moins de cache que ce qui semble tentant et ajoutez des couches selon les mesures, en documentant la stratégie d'invalidation pour chacune. Considérez la mise en cache agressive des données sensibles comme une décision qui doit être justifiée et non comme une décision par défaut. La règle d’or est que personne ne devrait être capable d’expliquer pourquoi un écran affiche d’anciennes informations simplement en haussant les épaules.

Que faut-il prendre en compte dans la décision

Le cache, le streaming et le suspense, ensemble, ont rendu les performances perçues bien meilleures que ce qui était possible avec les astuces de finition de la génération précédente. Le gain est concret : des pages qui apparaissent par parties, un travail qui ne se répète pas, des attentes qui ne ralentissent pas l'utilisateur dans le pire des cas.

Le contrepoint est que ces gains proviennent de décisions prises tôt, concernant les limites des composants et la tolérance aux données obsolètes. Les reporter à la fin n’est pas neutre : c’est renoncer au streaming et pousser le cache sur le territoire des anciennes données silencieuses. La performance perçue est devenue architecture, avec tout ce que cela implique de discipline de planification et d’invalidation.

Si vous définissez la pile d'une application Next.js et que vous souhaitez éviter le cauchemar du cache que personne ne comprend, cela vaut la peine de concevoir la stratégie de données avant le premier écran. J'ai beaucoup discuté de cette conception ; Retrouvez-moi dans les commentaires ou sur les réseaux pour échanger des idées.

A lire aussi