Composition, mise en cache et architecture dans Next.js moderne

VVercel
Computing/Software

Transcript

00:00:00(musique entraînante) - Bonjour à tous.
00:00:06Je m'appelle Aurora.
00:00:07Je suis développeuse web, originaire de Norvège.
00:00:09Je suis consultante chez Crane Consulting et je développe activement avec le routeur d'applications Next.js dans mon projet de conseil actuel.
00:00:16Aujourd'hui,
00:00:17je vais vous présenter des modèles de composition,
00:00:19de mise en cache et d'architecture dans Next.js moderne qui vous aideront à garantir l'évolutivité et la performance.
00:00:24Permettez-moi d'abord de rafraîchir le concept le plus fondamental de cette présentation : le rendu statique et dynamique.
00:00:30Nous les rencontrons tous deux dans le routeur d'applications Next.js.
00:00:33Le rendu statique nous permet de créer des sites web plus rapides,
00:00:36car le contenu pré-rendu peut être mis en cache et distribué mondialement,
00:00:39garantissant un accès plus rapide aux utilisateurs.
00:00:42Par exemple, le site web de la Next.js Conf.
00:00:46Le rendu statique réduit la charge du serveur,
00:00:48car le contenu n'a pas besoin d'être généré pour chaque requête utilisateur.
00:00:51Le contenu pré-rendu est également plus facile à indexer pour les robots des moteurs de recherche,
00:00:56car il est déjà disponible au chargement de la page.
00:00:58Le rendu dynamique,
00:00:59en revanche,
00:01:00permet à notre application d'afficher des données en temps réel ou fréquemment mises à jour.
00:01:05Il nous permet également de proposer du contenu personnalisé,
00:01:07comme des tableaux de bord et des profils utilisateur.
00:01:09Par exemple, le tableau de bord Vercel.
00:01:12Avec le rendu dynamique,
00:01:13nous pouvons accéder à des informations qui ne peuvent être connues qu'au moment de la requête.
00:01:16Dans ce cas,
00:01:17quel utilisateur accède à son tableau de bord,
00:01:19c'est-à-dire moi.
00:01:20Certaines API peuvent provoquer un rendu dynamique de la page.
00:01:25L'utilisation des props `params` et `search params` passées aux pages ou de leurs hooks équivalents entraînera un rendu dynamique.
00:01:32Cependant,
00:01:32avec `params`,
00:01:33nous pouvons prédéfinir un ensemble de pages pré-rendues à l'aide de `static params` génériques,
00:01:36et nous pouvons également mettre en cache les pages au fur et à mesure qu'elles sont générées par les utilisateurs.
00:01:40De plus,
00:01:41la lecture des cookies et en-têtes de requête entrants fera basculer la page en rendu dynamique.
00:01:46Contrairement aux `params`,
00:01:47cependant,
00:01:48tenter de mettre en cache ou de pré-rendre quoi que ce soit en utilisant des en-têtes ou des cookies provoquera des erreurs lors de la compilation,
00:01:52car ces informations ne peuvent pas être connues à l'avance.
00:01:56Enfin,
00:01:56l'utilisation de `fetch` avec une configuration de cache de données `no store` forcera également le rendu dynamique.
00:02:00Ce sont les principales.
00:02:01Il existe quelques autres API qui peuvent provoquer un rendu dynamique,
00:02:04mais ce sont celles que nous rencontrons le plus souvent..
00:02:06Dans les versions précédentes de Next,
00:02:08une page était rendue soit entièrement statique,
00:02:11soit entièrement dynamique.
00:02:13Une seule API dynamique sur une page fera basculer toute la page en rendu dynamique.
00:02:17Par exemple,
00:02:18une simple vérification d'authentification pour la valeur d'un cookie.
00:02:20En utilisant les composants serveur React avec `Suspense`,
00:02:23nous pouvons diffuser du contenu dynamique,
00:02:25comme une bannière de bienvenue personnalisée ou des recommandations,
00:02:28dès qu'ils sont prêts,
00:02:29et ne fournir que des `fallbacks` avec `Suspense` tout en affichant du contenu statique,
00:02:32comme une newsletter.
00:02:34Cependant,
00:02:34une fois que nous ajoutons plusieurs composants asynchrones sur une page dynamique,
00:02:38comme un produit vedette,
00:02:40ils s'exécuteraient également au moment de la requête,
00:02:42même s'ils ne dépendaient pas d'API dynamiques.
00:02:45Ainsi,
00:02:45pour éviter de bloquer le chargement initial de la page,
00:02:48nous suspendrions et diffuserions également ces composants,
00:02:50ce qui impliquerait un travail supplémentaire,
00:02:52la création de squelettes et des préoccupations concernant des éléments comme le décalage de mise en page.
00:02:56Cependant,
00:02:57les pages sont souvent un mélange de contenu statique et dynamique.
00:03:01Par exemple,
00:03:01une application e-commerce dépendante des informations utilisateur tout en contenant majoritairement des données statiques.
00:03:07Être forcé de choisir entre eux,
00:03:09entre statique ou dynamique,
00:03:10entraîne beaucoup de traitement redondant sur le serveur pour du contenu qui ne change jamais ou très rarement,
00:03:17et n'est pas optimal pour la performance.
00:03:19Pour résoudre ce problème,
00:03:21lors de la Next.js Conf de l'année dernière,
00:03:23la directive `use cache` a été annoncée.
00:03:26Et cette année,
00:03:26comme nous l'avons vu lors de la keynote,
00:03:28elle est disponible dans Next.js 16.
00:03:30Ainsi,
00:03:31avec `use cache`,
00:03:32les pages ne seront plus contraintes à un rendu statique ou dynamique.
00:03:36Elles peuvent être les deux.
00:03:37Et Next.js n'a plus à deviner la nature d'une page en fonction de son accès à des éléments comme les `params`.
00:03:43Tout est dynamique par défaut et `use cache` nous permet d'opter explicitement pour la mise en cache.
00:03:47`Use cache` permet une mise en cache composable.
00:03:51Nous pouvons marquer une page,
00:03:52un composant React ou une fonction comme pouvant être mis en cache.
00:03:55Ici,
00:03:56nous pouvons effectivement mettre en cache le composant des produits vedettes,
00:03:59car il n'a pas besoin de la requête et du traitement,
00:04:02et n'utilise pas d'API dynamiques.
00:04:03Et ces segments mis en cache peuvent être pré-rendus et inclus dans le shell statique avec le pré-rendu partiel,
00:04:08ce qui signifie que les produits vedettes sont désormais disponibles au chargement de la page et n'ont pas besoin d'être diffusés.
00:04:14Maintenant que nous avons ces connaissances de base importantes,
00:04:18passons à une démonstration.
00:04:19Une amélioration d'une base de code présentant des problèmes courants souvent rencontrés dans les applications Next.js.
00:04:24Ces problèmes incluent le `prop drilling` profond,
00:04:26rendant difficile la maintenance et la refactorisation des fonctionnalités,
00:04:28un JavaScript côté client redondant et des composants volumineux avec de multiples responsabilités,
00:04:32ainsi qu'un manque de rendu statique,
00:04:33entraînant des coûts de serveur supplémentaires et une performance dégradée.
00:04:36Alors oui, commençons.
00:04:37Et donnez-moi une seconde ici.
00:04:50Très bien, parfait.
00:04:54C'est une application très simple.
00:04:56Elle est inspirée d'une plateforme e-commerce.
00:04:59Et laissez-moi faire une première démonstration ici.
00:05:01Je peux donc charger cette page.
00:05:03J'ai du contenu comme ce produit vedette.
00:05:06J'ai des catégories vedettes,
00:05:07différentes données de produits.
00:05:09Il y a aussi cette page 'Parcourir tout' où je peux voir tous les produits de la plateforme et naviguer entre eux.
00:05:20Ensuite,
00:05:20nous avons cette page 'À propos' qui est juste statique.
00:05:24Je peux aussi me connecter en tant qu'utilisateur.
00:05:27Et cela me connectera à mon compte utilisateur.
00:05:30Et aussi obtenir ensuite du contenu personnalisé sur mon tableau de bord ici.
00:05:33Comme par exemple,
00:05:34des produits recommandés ou ces réductions personnalisées ici.
00:05:38Remarquez ici, il y a un assez bon mélange.
00:05:42Oh, et aussi une page que j'ai oublié de vous montrer.
00:05:45La page produit, la plus importante.
00:05:47Ici aussi,
00:05:48nous pouvons voir les informations sur le produit et les enregistrer si nous le souhaitons pour notre utilisateur.
00:05:52Remarquez donc qu'il y a un assez bon mélange de contenu statique et dynamique dans cette application en raison de toutes nos fonctionnalités dépendantes de l'utilisateur.
00:05:59Jetons également un coup d'œil au code, qui se trouve ici.
00:06:05J'utilise le routeur d'applications ici,
00:06:07bien sûr,
00:06:07dans Next.js 16.
00:06:08J'ai toutes mes différentes pages,
00:06:10comme la page 'À propos',
00:06:11la page 'Tout',
00:06:12notre page produit.
00:06:13J'ai aussi...
00:06:14j'utilise le découpage par fonctionnalité ici pour garder mon dossier d'application propre..
00:06:17J'ai différents composants et requêtes qui communiquent avec ma base de données via Prisma.
00:06:23Alors oui, et j'ai délibérément ralenti tout cela.
00:06:25C'est pourquoi nous avons cette très longue phase de chargement,
00:06:28juste pour que nous puissions plus facilement voir ce qui se passe.
00:06:31Les problèmes courants sur lesquels nous voulions travailler ici et que nous avons réellement dans cette application étaient le `prop drilling`,
00:06:37rendant difficile la maintenance et la refactorisation des fonctionnalités,
00:06:40l'accès au JavaScript côté client,
00:06:42et le manque de rendu statique entraînant des coûts de serveur supplémentaires et une performance intégrée.
00:06:47L'objectif de cette démonstration est donc d'améliorer cette application avec des modèles intelligents de composition,
00:06:53de mise en cache et d'architecture pour corriger ces problèmes courants et la rendre plus rapide,
00:06:58plus évolutive et plus facile à maintenir.
00:07:01Alors, commençons par cela.
00:07:02Le premier problème que nous voulons résoudre est en fait lié au `prop drilling`.
00:07:05Et ce serait ici dans la page.
00:07:10Remarquez ici, j'ai cette variable `logged in` en haut.
00:07:15Et vous pouvez voir que je la transmets à quelques composants.
00:07:17Elle a en fait été transmise à plusieurs niveaux jusqu'à cette bannière personnalisée.
00:07:20Cela va donc rendre difficile la réutilisation des éléments ici,
00:07:23car nous avons toujours cette dépendance `logged in` pour notre bannière de bienvenue.
00:07:28Avec les composants serveur,
00:07:29la meilleure pratique serait de pousser la récupération de données vers les composants qui l'utilisent et de résoudre les promesses plus profondément dans l'arbre.
00:07:37Et pour que cela soit authentifié,
00:07:39tant que cela utilise `fetch` ou quelque chose comme `React cache`,
00:07:42nous pouvons dupliquer plusieurs appels de ceci,
00:07:44et nous pouvons simplement le réutiliser partout où nous le souhaitons à l'intérieur de nos composants.
00:07:48Ce serait donc tout à fait réutilisable.
00:07:51Nous pouvons donc maintenant déplacer cela dans la section personnalisée ici.
00:07:54Et nous n'aurons plus besoin de cette prop.
00:07:57Et il suffit de le mettre directement – oups – ici.
00:08:01Et nous n'aurons plus besoin de le passer.
00:08:04Et puisque nous déplaçons maintenant cet appel asynchrone dans la section personnalisée,
00:08:07nous ne bloquons plus la page.
00:08:09Nous pouvons aller de l'avant et le suspendre avec un simple `Suspense` ici.
00:08:13Et nous n'aurons pas besoin de ce `fallback`.
00:08:16Quant à la bannière de bienvenue,
00:08:19je suppose que nous allons faire de même.
00:08:22Mais essayer d'utiliser la variable ou la valeur `logged in` ici,
00:08:26ça ne marche pas,
00:08:26n'est-ce pas ?
00:08:27Parce que c'est un composant client.
00:08:29Nous devons donc résoudre cela d'une manière différente.
00:08:30Et nous allons utiliser un modèle assez intelligent ici pour résoudre cela.
00:08:33Nous allons en fait aller dans le `layout` et tout envelopper ici avec un `auth provider`.
00:08:39Je vais donc mettre cela autour de toute mon application ici et obtenir cette variable `logged in` ici.
00:08:45Et je ne veux absolument pas bloquer tout mon `root layout`.
00:08:48Allons-y et supprimons l'`await` ici.
00:08:50Et passons simplement cela comme une promesse à ce `auth provider`.
00:08:55Et cela peut juste contenir cette promesse.
00:08:57Elle peut juste rester là à attendre que nous soyons prêts à la lire.
00:09:01Maintenant que c'est configuré.
00:09:03Cela signifie que nous pouvons en fait aller de l'avant et nous débarrasser de cette prop,
00:09:08tout d'abord.
00:09:09Et nous nous débarrasserons de celle-ci qui descend jusqu'à la bannière personnalisée.
00:09:12Et nous nous débarrasserons également du `prop drilling` ici ou de la signature.
00:09:16Et maintenant,
00:09:17nous pouvons utiliser ce `auth provider` pour récupérer cette valeur `logged in` localement à l'intérieur de la bannière personnalisée avec `use auth` avec ce fournisseur que nous venons de créer.
00:09:26Et la lire avec `use`.
00:09:28Cela fonctionnera donc en quelque sorte de manière à ce que nous devions le suspendre pendant qu'il se résout.
00:09:33J'ai donc maintenant co-localisé cette petite récupération de données à l'intérieur de la bannière personnalisée.
00:09:37Et je n'ai pas à passer ces props partout.
00:09:40Et pendant que cela se résout,
00:09:41allons-y et suspendons également celui-ci avec un `fallback`.
00:09:44Et faisons juste une bannière générale ici pour éviter tout décalage cumulatif étrange.
00:09:51Et enfin, débarrassons-nous également de celui-ci.
00:09:53Donc maintenant, cette bannière de bienvenue est composable.
00:09:58Elle est réutilisable.
00:09:59Nous n'avons pas de props ou de dépendances étranges sur la page d'accueil.
00:10:02Et puisque nous pouvons le réutiliser si facilement,
00:10:06allons-y et ajoutons-le également à cette page de navigation ici,
00:10:10qui sera là.
00:10:11Et je peux simplement l'utiliser ici sans aucune dépendance.
00:10:15Ainsi,
00:10:16grâce à ces modèles,
00:10:17nous sommes en mesure de maintenir une bonne architecture de composants en utilisant `React cache`,
00:10:25`React use`,
00:10:25et de rendre nos composants plus utilisables et composables.
00:10:30Très bien.
00:10:31Abordons le prochain défi courant,
00:10:33qui serait un JavaScript côté client excessif et des composants volumineux avec de multiples responsabilités.
00:10:40En fait, c'est aussi sur la page 'Tout' ici.
00:10:43Et encore une fois,
00:10:44nous devrons travailler sur cette bannière de bienvenue.
00:10:46C'est actuellement un composant client.
00:10:48Et la raison pour laquelle c'est un composant client est que j'ai cet état `dismissed` très simple ici.
00:10:53Je peux juste cliquer dessus.
00:10:54C'est une belle interaction UI.
00:10:56C'est bon.
00:10:57Ce qui n'est pas si bien,
00:10:58cependant,
00:10:59c'est qu'à cause de cela,
00:11:00je convertis tout ce composant en un composant côté client ou un composant client.
00:11:04Et j'utilise même `swr` pour la récupération côté client.
00:11:07J'ai maintenant cette couche API ici.
00:11:08Je n'ai plus de sécurité de type dans mes données.
00:11:11Oui, ce n'est pas nécessaire.
00:11:12Et nous brisons également la séparation des préoccupations ici parce que nous mélangeons la logique UI avec les données.
00:11:18Alors allons-y et utilisons un autre modèle intelligent pour résoudre cela.
00:11:21Il s'appelle le `donut pattern`.
00:11:23En gros,
00:11:23ce que je vais faire,
00:11:24c'est l'extraire dans un `wrapper` côté client.
00:11:27Alors créons un nouveau composant ici.
00:11:29Et appelons-le `banner container`.
00:11:32Et cela va contenir notre logique interactive avec la directive `use client`.
00:11:37Nous pouvons créer la signature.
00:11:38Nous pouvons coller tout ce que nous avions plus tôt.
00:11:42Et au lieu d'utiliser ces bannières,
00:11:44je vais juste insérer une prop ici,
00:11:46qui sera les `children`.
00:11:48C'est pourquoi on l'appelle le `donut pattern`.
00:11:50Nous créons simplement cette logique UI `wrapper` autour du contenu rendu par le serveur,
00:11:54ou cela pourrait être du contenu rendu par le serveur.
00:11:56Et puis,
00:11:57puisque nous n'avons plus cette dépendance côté client,
00:11:59nous pouvons aller de l'avant et supprimer le `use client`.
00:12:01Nous pouvons utiliser notre fonction asynchrone `isAuth` ici à la place.
00:12:06Nous pouvons en faire un composant serveur asynchrone.
00:12:09Nous pouvons même remplacer la récupération côté client par la récupération côté serveur.
00:12:11Alors,
00:12:12laissez-moi récupérer directement les données de réduction ici.
00:12:16Données de réduction.
00:12:18Et utilisons simplement notre modèle mental habituel comme avant avec la sécurité de type.
00:12:24Et cela signifie que je peux aussi supprimer cette couche API avec laquelle je ne veux pas travailler de toute façon.
00:12:29Enfin,
00:12:29pour `isLoading`,
00:12:30nous pouvons simplement exporter une nouvelle bannière de bienvenue ici avec notre conteneur de bannière `donut pattern` contenant du contenu rendu par le serveur.
00:12:38Et cela signifie que nous n'avons plus besoin de cet `isLoading`.
00:12:40Nous avons donc essentiellement refactorisé tout cela en un composant serveur et extrait un point de logique UI.
00:12:46Mais qu'est-ce que c'est ?
00:12:48Il semble que j'aie une autre erreur.
00:12:51C'est en fait à cause de `Motion`.
00:12:53Utilisez `Motion`.
00:12:54C'est une très bonne bibliothèque d'animation,
00:12:56mais elle nécessite la directive `useClient`.
00:12:59Et encore une fois,
00:13:00nous n'avons pas à rendre cela `useClient` juste pour l'animation.
00:13:03Nous pouvons créer,
00:13:04encore une fois,
00:13:05un `wrapper donut pattern` et simplement extraire des `wrappers` pour ces animations.
00:13:10Et cela signifie que nous n'avons pas à convertir quoi que ce soit ici en côté client.
00:13:14Et il me manque probablement quelque chose ici.
00:13:17Oui.
00:13:18Voilà.
00:13:21Donc maintenant, tout ici a été converti en serveur.
00:13:23Nous avons la même interaction.
00:13:24Nous avons toujours notre logique interactive ici,
00:13:26mais maintenant nous avons cette seule façon de récupérer des données.
00:13:29Et nous avons beaucoup moins de JS côté client.
00:13:31En fait,
00:13:32j'utilise moi-même ce `donut pattern` pour cet `UI boundary helper`,
00:13:40qui ressemble à ceci.
00:13:42Vous voyez ?
00:13:43Donc cela montre,
00:13:44encore une fois,
00:13:44ce que je veux dire,
00:13:45n'est-ce pas ?
00:13:45Avec le `donut pattern`,
00:13:46nous avons ce composant client autour d'un composant serveur.
00:13:49J'ai également marqué beaucoup de mes autres composants avec cet `UI helper` ici.
00:13:53Ici aussi, j'ai plus de composants serveur.
00:13:56Allons-y et améliorons ceux-là aussi,
00:13:58puisque nous devenons assez bons à cela maintenant.
00:14:01Ils sont dans le pied de page.
00:14:04Ces catégories...
00:14:04je veux dire,
00:14:05j'ai ce joli composant qui récupère ses propres données..
00:14:08Et je voulais juste ajouter ce truc `showMore`,
00:14:11juste au cas où ça deviendrait vraiment long.
00:14:14Et avec le `donut pattern`,
00:14:15je peux juste envelopper un composant `showMore` ici.
00:14:20Et cela contiendra ma logique UI.
00:14:23Et ça ressemble à ça, n'est-ce pas ?
00:14:27Plutôt cool.
00:14:28Et cela contient maintenant la logique client,
00:14:31nous permettant d'utiliser l'état.
00:14:33Nous utilisons le `children count` et `to array` pour découper cela.
00:14:36Et ce qui est si cool ici,
00:14:37c'est que ces deux-là sont maintenant des composants entièrement composables et réutilisables qui fonctionnent ensemble comme ceci.
00:14:42C'est vraiment la beauté de ces modèles que nous apprenons ici.
00:14:45Vous pouvez l'utiliser pour n'importe quoi.
00:14:50Je l'utilise aussi pour cette modale ici.
00:14:52Oui,
00:14:52rappelez-vous juste cela la prochaine fois que vous envisagerez d'ajouter une logique client à vos composants serveur.
00:14:59OK, nous connaissons le `donut pattern`.
00:15:01Nous savons comment l'utiliser pour créer ces composants composables et éviter le JS côté client,
00:15:07nous pouvons donc passer au dernier problème.
00:15:10Laissez-moi fermer cela à nouveau.
00:15:13Ce serait donc un manque de stratégies de rendu statique,
00:15:17n'est-ce pas ?
00:15:18En regardant ma sortie de compilation,
00:15:20j'ai en fait chaque page qui est une page dynamique ici.
00:15:24Cela signifie donc que chaque fois que je charge quelque chose ici,
00:15:27cela va s'exécuter pour chaque utilisateur.
00:15:29Désolé.
00:15:30Chaque utilisateur qui est ouvert à cela va obtenir cet état de chargement.
00:15:33Cela va gaspiller des coûts de serveur,
00:15:35rendant la performance pire.
00:15:37Et cela signifie aussi que quelque chose à l'intérieur de mes pages provoque un rendu dynamique ou force un rendu dynamique pour toutes mes pages.
00:15:45En fait, c'est à l'intérieur de mon `root layout`.
00:15:49Je ne sais pas si vous avez déjà rencontré cela.
00:15:51C'est ici.
00:15:53Dans mon en-tête, j'ai ce profil utilisateur.
00:15:57Et cela utilise,
00:15:57bien sûr,
00:15:58des cookies pour obtenir l'utilisateur actuel,
00:16:00et cela signifie que tout le reste est également rendu dynamiquement.
00:16:02Parce que encore une fois,
00:16:03les pages peuvent être soit dynamiques,
00:16:05soit statiques,
00:16:05n'est-ce pas ?
00:16:06C'est un problème assez courant et quelque chose qui a déjà été résolu dans les versions précédentes de Next,
00:16:11alors voyons ce que nous pourrions faire.
00:16:13Une chose que nous pourrions faire est de créer un groupe de routes et de diviser notre application en sections statiques et dynamiques,
00:16:20ce qui me permettrait d'extraire ma page 'À propos'.
00:16:23Je pourrais la rendre statiquement.
00:16:25C'est acceptable pour certaines applications,
00:16:27mais dans mon cas,
00:16:28la page importante est la page produit,
00:16:30et celle-ci est toujours dynamique,
00:16:32donc pas vraiment utile.
00:16:33Que diriez-vous de cette stratégie ?
00:16:35Donc ici,
00:16:35je crée ce paramètre de contexte de requête encodant un certain état dans mon URL,
00:16:40et ensuite je peux utiliser `generate static params` pour générer toutes les différentes variantes de mes pages.
00:16:46Cela me permettrait en fait,
00:16:48combiné à la récupération côté client des données utilisateur,
00:16:51de mettre cela en cache sur ma page produit.
00:16:54Certainement un modèle viable.
00:16:55C'est recommandé par le SDK Vercel Flags,
00:16:58appelé le `precompute pattern`,
00:17:00je crois.
00:17:00Mais c'est vraiment complexe,
00:17:01et j'ai plusieurs façons de récupérer des données.
00:17:04Et en fait,
00:17:04je ne veux pas réécrire toute mon application pour cela.
00:17:07Et si nous n'avions pas à faire toutes ces solutions de contournement ?
00:17:10Et s'il y avait un moyen plus simple ?
00:17:12Eh bien, il y en a un.
00:17:14Revenons à notre application.
00:17:17Nous pouvons donc en fait aller dans la configuration Next et simplement activer les composants de cache.
00:17:23Oh, super.
00:17:25OK,
00:17:25et ce que cela fait,
00:17:26comme vous le savez d'après la keynote,
00:17:29c'est que toutes nos appels asynchrones seront optés pour le temps de requête ou dynamique.
00:17:34Et cela nous donnera également des erreurs chaque fois que nous aurons un appel asynchrone non suspendu,
00:17:39et cela nous donnera cette directive `use cache` que nous pourrons utiliser pour mettre en cache de manière granulaire une page,
00:17:46une fonction ou un composant.
00:17:48Alors oui, allons-y et utilisons cela.
00:17:51Nous pouvons commencer par la page d'accueil ici.
00:17:55Jetons un coup d'œil.
00:17:56Donc encore une fois,
00:17:57j'ai ce mélange de contenu statique et dynamique.
00:17:59J'ai ma bannière de bienvenue pour moi,
00:18:01quelque chose pour vous aussi pour moi.
00:18:03Jetons un coup d'œil à cela avec cet `UI helper` à nouveau.
00:18:06Donc par exemple,
00:18:07la bannière est rendue dynamiquement avec ceci ici.
00:18:10Alors que j'ai marqué cela comme rendu hybride parce que le héros,
00:18:14il récupère cette chose asynchrone et ça va assez lentement.
00:18:18Mais cela ne dépend d'aucune sorte de données utilisateur ou d'API dynamiques.
00:18:21Cela signifie donc que tout ce qui est rendu en hybride ici peut en fait être réutilisé à travers les requêtes et à travers les utilisateurs.
00:18:27Et nous pouvons utiliser la directive `use cache` sur cela.
00:18:30Alors ajoutons la directive `use cache` ici et marquons-la comme mise en cache.
00:18:35Et cela me permettra de...
00:18:37chaque fois que je recharge cette page...
00:18:40je n'ai pas sauvegardé cela..
00:18:43Voilà.
00:18:44Cela ne rechargera pas cette partie car elle est mise en cache.
00:18:47C'est maintenant statique, n'est-ce pas ?
00:18:49Et il existe également d'autres API connexes comme le `cache tag` pour me permettre de typer cela ou de valider l'entrée de cache spécifique de manière granulaire ou de définir ma période de révélation.
00:19:01Mais pour cette démo,
00:19:02concentrons-nous simplement sur la directive simple.
00:19:05Maintenant que j'ai cette directive `use cache`,
00:19:07je peux en fait supprimer ma `suspense boundary` autour de ce héros.
00:19:10Et cela signifie...
00:19:12eh bien,
00:19:12ce que cela fera,
00:19:13c'est que le pré-rendu partiel pourra en fait inclure cela dans le shell pré-rendu statiquement afin que ce héros soit,
00:19:20dans ce cas,
00:19:21une partie de ma sortie de compilation..
00:19:23Faisons la même chose pour tout le reste sur cette page qui peut être partagé.
00:19:28Par exemple, j'ai ces catégories vedettes ici.
00:19:31Allons-y et faisons de même là-bas.
00:19:33Et ajoutons la directive `use cache` et marquons-la comme mise en cache.
00:19:37Comme ça.
00:19:39Et nous pouvons supprimer la `suspense boundary`.
00:19:40Nous n'en aurons plus besoin.
00:19:43Pareil pour les produits vedettes.
00:19:44Ajoutons `use cache` et marquons-le comme mis en cache.
00:19:48Oups.
00:19:50Et ensuite supprimons la `suspense boundary`.
00:19:52Remarquez donc la quantité de complexité que je suis capable de supprimer ici.
00:19:55Je n'ai pas à me soucier de mes squelettes,
00:19:57de mon décalage de mise en page cumulatif que je faisais avant.
00:20:00Et la page n'est plus...
00:20:01ou nous n'avons plus cette limitation statique versus dynamique au niveau de la page..
00:20:07Donc maintenant,
00:20:08quand je charge cette page,
00:20:10vous verrez que tout est mis en cache,
00:20:12sauf ce contenu véritablement spécifique à l'utilisateur.
00:20:16Exactement.
00:20:18C'est plutôt cool.
00:20:19Allons sur la page 'Parcourir' et faisons la même chose là-bas.
00:20:24Oui.
00:20:25J'ai déjà marqué toutes mes limites ici pour que vous puissiez facilement comprendre ce qui se passe.
00:20:29Et je veux au moins mettre en cache ces catégories.
00:20:33Il semble que j'aie une erreur, cependant.
00:20:37Peut-être reconnaissez-vous cela.
00:20:38Cela signifie donc que j'ai une route bloquante.
00:20:40Et je n'utilise pas de `suspense boundary` alors que je devrais le faire.
00:20:43En rafraîchissant cela, c'est vrai, hein ?
00:20:46C'est vraiment lent.
00:20:47Et cela cause des problèmes de performance et une mauvaise UX.
00:20:50C'est génial.
00:20:51`Use cache` ou les composants de cache m'aident à identifier mes routes bloquantes.
00:20:55Voyons en fait ce qui se passe à l'intérieur.
00:20:57C'est donc le problème, n'est-ce pas ?
00:20:59Je récupère ces catégories au niveau supérieur et je n'ai pas de `suspense boundary` au-dessus.
00:21:03En gros, nous devons faire un choix.
00:21:05Soit nous ajoutons une `suspense boundary` au-dessus,
00:21:07soit nous optons pour la mise en cache.
00:21:09Faisons d'abord la chose simple et ajoutons juste un `loading.tsx` ici.
00:21:12Et ajoutons une page de chargement ici,
00:21:17une belle UI de squelette.
00:21:21C'est plutôt bien.
00:21:21Cela a résolu l'erreur,
00:21:22mais je n'ai rien d'utile qui se passe sur cette page pendant que j'attends.
00:21:25Je ne peux même pas chercher.
00:21:27Donc avec les composants de cache, le dynamique est comme...
00:21:30ou statique versus dynamique est comme une échelle..
00:21:33Et c'est à nous de décider combien de statique nous voulons dans nos pages.
00:21:37Alors déplaçons cette page plus vers le statique et supprimons à nouveau ce `loading.tsx`.
00:21:43Et ensuite utilisons les modèles que nous apprenions plus tôt pour pousser cette récupération de données dans le composant et la co-localiser avec l'UI.
00:21:50Alors déplaçons cela dans mes filtres de catégories responsives ici.
00:21:54J'en ai deux à cause du design responsive.
00:21:57Je peux en fait aller de l'avant et juste l'ajouter ici.
00:22:01Oups.
00:22:03Et importons cela.
00:22:05Je n'ai plus besoin de cette prop.
00:22:06En fait, mon composant devient plus composable.
00:22:09Et au lieu de le suspendre,
00:22:11ajoutons simplement la directive `use cache`.
00:22:14Et cela devrait suffire.
00:22:16Remarquez donc comment je suis forcé de réfléchir davantage à l'endroit où je résous mes promesses et d'améliorer réellement mon architecture de composants grâce à cela.
00:22:24Je n'ai pas besoin de suspendre cela.
00:22:25Cela sera juste inclus dans le `static shell` ici.
00:22:28La liste des produits, laissez-moi juste la garder fraîche.
00:22:35Pour que je puisse la recharger à chaque fois.
00:22:37Alors que les catégories en bas,
00:22:39je veux aussi les mettre en cache.
00:22:41Alors allons-y et allons au pied de page.
00:22:44Et puisque j'utilise le `donut pattern` ici,
00:22:47cela peut en fait être mis en cache même si c'est à l'intérieur de cette partie de l'UI qui est interactive.
00:22:54C'est donc tout à fait bien.
00:22:55Ce modèle n'était donc pas seulement bon pour la composition,
00:22:57mais aussi pour la mise en cache.
00:22:58Je pense que j'ai une autre erreur là-bas.
00:23:03Voyons ce que c'est.
00:23:04J'ai toujours cette erreur.
00:23:08C'est en fait à cause de ces `search params`.
00:23:10Les `search params`,
00:23:10comme nous le savons,
00:23:11sont une API dynamique.
00:23:12Je ne peux pas mettre cela en cache.
00:23:13Mais je peux le résoudre plus profondément pour révéler plus de mon UI et la rendre statique.
00:23:18Alors allons-y et déplaçons cela,
00:23:20passons-le comme une promesse à la liste des produits.
00:23:24Nous allons le typer comme une promesse ici, comme ça.
00:23:30Résolvons-le à l'intérieur de la liste des produits,
00:23:33utilisons les `search parameters` résolus ici et ici.
00:23:36Et puisque cela est suspendu ici, l'erreur disparaîtra.
00:23:40Donc en rechargeant cela,
00:23:42la seule chose qui se recharge ici est juste la partie que j'ai spécifiquement choisie pour être dynamique.
00:23:48Tout le reste peut être mis en cache.
00:23:49Et cela signifie que je peux interagir avec ma bannière ou même chercher parce que cette partie a déjà été pré-rendue.
00:23:57Très bien,
00:23:58faisons la dernière page ici,
00:24:00qui est la page produit,
00:24:02qui est la plus difficile et la plus importante.
00:24:05C'est vraiment mauvais en ce moment.
00:24:08C'est super important pour une plateforme e-commerce,
00:24:10apparemment.
00:24:11Très bien, allons-y et réparons celui-là aussi.
00:24:15Donc ici, j'ai cette page produit.
00:24:18Commençons par mettre en cache juste le contenu réutilisable ici,
00:24:21par exemple,
00:24:22le produit lui-même.
00:24:23Et ajoutons juste `use cache` ici et marquons-le comme mis en cache.
00:24:27Cela devrait être bon.
00:24:28Cela signifie que nous pouvons supprimer la `suspense boundary` ici.
00:24:33Très bien,
00:24:33et cela ne se recharge plus à chaque requête ici,
00:24:37n'est-ce pas ?
00:24:38Pour les détails du produit, faisons la même chose.
00:24:40Ajoutons `use cache`.
00:24:41Marquons-le comme mis en cache et voyons si cela fonctionnera aussi.
00:24:47Non.
00:24:48En fait, c'est une erreur différente.
00:24:50Cela me dit que j'essaie d'utiliser des API dynamiques à l'intérieur de ce segment mis en cache.
00:24:54Et c'est vrai.
00:24:54J'utilise le bouton 'Enregistrer le produit', n'est-ce pas ?
00:24:56Cela m'a permis de cliquer et de basculer l'état enregistré.
00:25:00Alors, que pensez-vous que nous puissions faire avec cela ?
00:25:03Nous pouvons utiliser le `donut pattern` à nouveau.
00:25:06En fait,
00:25:06nous pouvons aussi insérer des segments dynamiques dans des segments de cache.
00:25:10Nous les intercalons donc comme avant, mais avec le cache.
00:25:12C'est plutôt cool.
00:25:14Allons-y et ajoutons les `children` ici comme ça.
00:25:19Et cela supprimera l'erreur.
00:25:21Et je peux simplement envelopper cela autour de ce seul segment dynamique de ma page ici,
00:25:26supprimer la `suspense boundary`,
00:25:28et ajouter juste une très petite UI de marque-page pour cette seule pièce dynamique de la page.
00:25:34Et voyons à quoi cela ressemble maintenant.
00:25:40Remarquez donc comment presque toute l'UI est disponible,
00:25:43mais j'ai ce petit morceau qui est dynamique,
00:25:46et c'est bien.
00:25:47Tout le reste est toujours là.
00:25:48Et laissons les avis dynamiques parce que nous pourrions les garder à jour.
00:25:53Il y a encore une erreur.
00:25:54Réglons cela rapidement.
00:25:56Encore une fois, ce sont les `params`.
00:25:58On m'aide à faire un choix : soit ajouter un `loading fallback`,
00:26:02soit mettre cela en cache.
00:26:04Utilisons simplement `generate static params` dans ce cas.
00:26:07Cela dépend un peu de votre cas d'utilisation et de votre ensemble de données.
00:26:10Mais pour ce cas,
00:26:10je vais juste ajouter quelques pages prédéfinies pré-rendues et ensuite simplement mettre en cache le reste au fur et à mesure qu'elles sont générées par les utilisateurs.
00:26:17Et cela supprimera mon erreur ici.
00:26:20Je pense donc que j'ai en fait terminé ma refactorisation.
00:26:22Allons-y et jetons un coup d'œil à la version déployée et voyons à quoi cela ressemble.
00:26:26Je viens de déployer cela sur Vercel.
00:26:27Et rappelez-vous,
00:26:29j'ai délibérément ralenti beaucoup de récupérations de données ici.
00:26:35Et pourtant,
00:26:36quand je charge cette page initialement,
00:26:39tout est déjà disponible.
00:26:40La seule chose ici,
00:26:41ce sont juste ces quelques segments dynamiques comme les réductions et le 'pour vous'.
00:26:46Pareil avec le 'parcourir tout'.
00:26:47Toute l'UI est déjà disponible.
00:26:50Et pour le produit lui-même, cela semble instantané.
00:26:54Et rappelez-vous,
00:26:55encore une fois,
00:26:56que tous ces segments de cache seront inclus avec le `static shell` avec le pré-rendu partiel.
00:27:00Et cela peut être pré-récupéré en utilisant le pré-chargement amélioré dans le nouveau routeur client de Next 16.
00:27:05Cela signifie donc que chaque navigation...
00:27:07cela semble si rapide, n'est-ce pas ?.
00:27:09Très bien,
00:27:10pour résumer,
00:27:11avec les composants de cache,
00:27:13il n'y a plus de statique versus dynamique.
00:27:17Et nous n'avons pas besoin d'éviter les API dynamiques ou de compromettre le contenu dynamique.
00:27:28Et nous pouvons ignorer ces hacks et solutions de contournement complexes utilisant plusieurs stratégies de récupération de données juste pour ce seul...
00:27:35ce `cache hit`, comme je vous l'ai montré..
00:27:37Donc dans Next.js moderne,
00:27:39dynamique versus statique est une échelle.
00:27:40Et nous décidons combien de statique nous voulons dans nos applications.
00:27:43Et tant que nous suivons certains modèles,
00:27:45nous pouvons avoir un modèle mental unique,
00:27:47qui est performant,
00:27:48composable et évolutif par défaut.
00:27:50Alors, revenons aux diapositives.
00:27:53Donc si vous n'êtes pas...
00:27:53nous ne sommes pas déjà impressionnés par la vitesse de cela,
00:27:55voici le score Lighthouse..
00:27:56J'ai donc collecté des données de terrain avec les Vercel Speed Insights.
00:28:00Nous avons donc un score de 100 sur toutes les pages les plus importantes,
00:28:03la page d'accueil,
00:28:04la page produit et la liste des produits,
00:28:06même si elles sont très dynamiques.
00:28:08Alors résumons enfin les modèles qui garantiront l'évolutivité et la performance dans les applications Next.js et nous permettront de tirer parti des dernières innovations et d'obtenir des scores comme celui-ci.
00:28:18Premièrement,
00:28:19nous pouvons affiner notre architecture en résolvant les promesses profondément dans l'arbre des composants et en récupérant les données localement à l'intérieur des composants en utilisant `React Cache` pour éviter le travail en double.
00:28:28Nous pouvons éviter le passage excessif de props aux composants client en utilisant des `context providers` combinés avec `React Use`.
00:28:35Deuxièmement,
00:28:35nous pouvons composer des composants serveur et client en utilisant le `donut pattern` pour réduire le JavaScript côté client,
00:28:40maintenir une séparation claire des préoccupations et permettre la réutilisation des composants.
00:28:43Et ce modèle nous permettra en outre de mettre en cache nos composants serveur composés plus tard.
00:28:50Et enfin,
00:28:50nous pouvons mettre en cache et pré-rendre avec `use cache`,
00:28:53soit par page,
00:28:53composant ou fonction,
00:28:54pour éliminer le traitement redondant,
00:28:56améliorer les performances et le SEO,
00:28:57et laisser le pré-rendu partiel rendre statiquement ces segments de l'application.
00:29:01Et si notre contenu est véritablement dynamique,
00:29:04nous pouvons le suspendre avec des `loading fallbacks` appropriés.
00:29:07Et rappelez-vous que tout cela est lié.
00:29:09Ainsi,
00:29:09meilleure est votre architecture,
00:29:10plus il est facile de composer,
00:29:11et plus il sera facile de mettre en cache et de pré-rendre avec les meilleurs résultats.
00:29:15Par exemple,
00:29:15la résolution d'API dynamiques profondément dans l'arbre vous permettra de créer un `static shell` partiellement rendu plus grand.
00:29:22Et avec cela,
00:29:22voici le dépôt de la version complétée de l'application.
00:29:25Il y a tellement de choses que je n'ai même pas montrées et que vous pouvez consulter.
00:29:29Et vous pouvez scanner le code QR pour trouver mes réseaux sociaux là-bas,
00:29:32ainsi que le dépôt,
00:29:33si vous ne voulez pas prendre une photo et le taper vous-même.
00:29:36Alors oui, c'est tout pour moi.
00:29:37Merci à la Next.js Conf de m'avoir accueillie ici.
00:29:39[MUSIQUE]

Key Takeaway

Next.js 16, avec la directive `use cache` et des modèles comme le 'donut pattern', transforme le rendu statique et dynamique en une échelle flexible, permettant une architecture performante et évolutive par défaut en optimisant la composition et la mise en cache des composants.

Highlights

Next.js 16 introduit la directive `use cache` pour une mise en cache granulaire et composable, permettant aux pages d'être à la fois statiques et dynamiques.

Le 'donut pattern' est une technique clé pour réduire le JavaScript côté client en enveloppant le contenu rendu par le serveur avec une logique d'interface utilisateur interactive côté client.

Une architecture améliorée implique de résoudre les promesses de récupération de données plus profondément dans l'arbre des composants et d'utiliser `React Cache` pour éviter le 'prop drilling' et la duplication du travail.

L'activation des composants de cache dans Next.js force les développeurs à identifier et à optimiser les routes bloquantes, améliorant ainsi l'architecture globale.

Les applications Next.js modernes peuvent atteindre des performances exceptionnelles, avec des scores Lighthouse de 100, même sur des pages très dynamiques, grâce à ces modèles d'optimisation.

Le rendu statique et dynamique dans Next.js est désormais perçu comme une échelle, où les développeurs peuvent décider du degré de staticité de leurs applications.

Timeline

Introduction et Rappel du Rendu

Aurora présente les modèles de composition, de mise en cache et d'architecture pour l'évolutivité et la performance dans Next.js moderne. Elle rafraîchit les concepts de rendu statique et dynamique, expliquant que le statique offre rapidité, mise en cache globale, réduction de la charge serveur et meilleur SEO (ex: site Next.js Conf). Le dynamique permet des données en temps réel, du contenu personnalisé (ex: tableau de bord Vercel) et l'accès à des informations connues uniquement à la requête. Cette section pose les bases essentielles pour comprendre les défis et les solutions abordées par la suite. Des API comme `params`, `search params`, la lecture des cookies et en-têtes, ou `fetch` avec `no store` peuvent forcer un rendu dynamique.

Défis du Rendu Traditionnel dans Next.js

Auparavant, une page était soit entièrement statique, soit entièrement dynamique, une seule API dynamique basculant toute la page en dynamique. L'utilisation de React Server Components avec `Suspense` permet de diffuser du contenu dynamique, mais l'ajout de plusieurs composants asynchrones sur une page dynamique entraînait un travail supplémentaire pour la diffusion et la gestion des `fallbacks` (squelettes, décalage de mise en page). Ce segment met en évidence les inefficiences et les compromis auxquels les développeurs étaient confrontés, justifiant l'introduction de nouvelles solutions. Les applications e-commerce, qui mélangent souvent contenu statique et dynamique, souffraient particulièrement de cette contrainte, entraînant un traitement serveur redondant et une performance sous-optimale.

L'Innovation `use cache` dans Next.js 16

La directive `use cache`, annoncée à la Next.js Conf et disponible dans Next.js 16, révolutionne le rendu en permettant aux pages d'être à la fois statiques et dynamiques. Next.js n'a plus à deviner la nature d'une page ; tout est dynamique par défaut, et `use cache` permet d'opter explicitement pour la mise en cache. Cette fonctionnalité est la pierre angulaire des améliorations de performance et d'architecture présentées dans la démonstration. `use cache` permet une mise en cache composable, où une page, un composant React ou une fonction peut être marqué comme cacheable. Par exemple, un composant de produits vedettes peut être mis en cache, pré-rendu et inclus dans le 'shell' statique via le pré-rendu partiel.

Démonstration : Problèmes Communs et Objectifs

La démonstration utilise une application e-commerce simple pour illustrer des problèmes courants : le 'prop drilling' profond, le JavaScript côté client redondant, les composants volumineux avec de multiples responsabilités, et le manque de rendu statique. L'application inclut des pages d'accueil, de navigation, de produit, 'À propos', et un tableau de bord utilisateur personnalisé, présentant un mélange de contenu statique et dynamique. Cette section prépare le terrain pour les solutions pratiques en montrant une base de code réelle avec des défauts typiques. L'objectif est d'améliorer l'application avec des modèles de composition, de mise en cache et d'architecture pour la rendre plus rapide, évolutive et facile à maintenir, en dépit d'un ralentissement délibéré des requêtes pour la démonstration.

Résolution du 'Prop Drilling' avec une Architecture Intelligente

Le problème du 'prop drilling' est illustré par une variable `logged in` passée à travers plusieurs composants, rendant la réutilisation difficile. La solution consiste à pousser la récupération de données vers les composants qui les utilisent et à résoudre les promesses plus profondément dans l'arbre, en utilisant `fetch` ou `React cache`. Un `AuthProvider` est créé dans le `root layout` pour envelopper l'application et passer la promesse d'authentification. Ce modèle améliore la réutilisabilité et la maintenabilité des composants, un aspect crucial pour les applications complexes. Le composant `CustomBanner` utilise `useAuth` et `use` pour lire localement la valeur d'authentification, et est enveloppé de `Suspense` pour éviter de bloquer le chargement de la page, rendant la bannière composable et réutilisable sans dépendances externes.

Optimisation du JavaScript Client avec le 'Donut Pattern'

Le 'donut pattern' est introduit pour résoudre le problème des composants serveur convertis en client à cause d'une petite logique interactive. La `WelcomeBanner`, initialement un composant client à cause d'un simple état `dismissed`, est refactorisée : la logique interactive est extraite dans un `BannerContainer` côté client qui prend des `children` (le contenu serveur). Ce modèle est essentiel pour maximiser le rendu côté serveur et minimiser la charge de JavaScript sur le client, améliorant ainsi les performances. La `WelcomeBanner` redevient un composant serveur asynchrone, récupérant directement les données côté serveur avec sécurité de type. Le même modèle est appliqué pour les animations `Motion` et les catégories du pied de page avec un composant `ShowMore`, démontrant la polyvalence du 'donut pattern'.

Mise en Cache Statique Granulaire avec `use cache`

L'application souffre d'un rendu entièrement dynamique, entraînant des coûts serveur et des performances dégradées, souvent à cause d'une API dynamique dans le `root layout` (ex: `UserProfile` utilisant des cookies). Les solutions traditionnelles (groupes de routes, 'precompute pattern') sont jugées complexes. Next.js 16 active les composants de cache, rendant les appels asynchrones dynamiques par défaut et introduisant la directive `use cache`. Cette section montre comment `use cache` simplifie l'optimisation du rendu en évitant les solutions de contournement complexes. En appliquant `use cache` à des composants comme `Hero`, `FeaturedCategories` et `FeaturedProducts` sur la page d'accueil, qui ne dépendent pas d'API dynamiques, ils sont pré-rendus statiquement et inclus dans le 'shell' statique via le pré-rendu partiel, réduisant la complexité et améliorant la vitesse de chargement.

Optimisation de la Page 'Parcourir tout'

La page 'Parcourir tout' présentait une route bloquante due à la récupération des catégories au niveau supérieur sans `Suspense`, causant une mauvaise UX. L'activation des composants de cache aide à identifier ces problèmes. La solution consiste à déplacer la récupération des données des catégories dans le composant `ResponsiveCategoryFilters` et à y appliquer `use cache`, l'incluant dans le 'static shell'. Cette partie illustre comment une meilleure architecture de composants est encouragée par les nouvelles fonctionnalités de Next.js. Les `search params`, étant une API dynamique, ne peuvent pas être mis en cache directement. Ils sont passés comme une promesse au composant `ProductList` et résolus plus profondément, permettant à la majeure partie de la page d'être pré-rendue statiquement tandis que la liste des produits reste dynamique pour la recherche.

Optimisation de la Page Produit et Résumé des Patterns

La page produit, cruciale et complexe, est optimisée en appliquant `use cache` au produit et aux détails du produit. Le 'donut pattern' est réutilisé pour encapsuler des segments dynamiques (ex: bouton 'Enregistrer le produit') dans des segments cacheables, permettant une UI presque entièrement disponible instantanément. `generate static params` est utilisé pour pré-rendre certaines pages produit et mettre en cache les autres à la volée. Cette section conclut la démonstration en montrant l'efficacité combinée de tous les modèles appris. La démonstration finale de l'application déployée sur Vercel montre des chargements instantanés et un score Lighthouse de 100 sur les pages importantes. Les trois modèles clés sont résumés : affiner l'architecture, composer les composants serveur/client avec le 'donut pattern', et mettre en cache/pré-rendre avec `use cache` et `Suspense` pour le contenu dynamique.

Community Posts

View all posts