24 mars 2025
Comment fonctionne l’API use avec Next 15 et React 19 ?

8 minutes de lecture

Le data fetching
et les mutations
sont les pierres angulaires de nombreux sites web. Pour les développeurs frontend, l'objectif est souvent de mettre en place des interfaces performantes tout en garantissant une expérience utilisateur fluide. Mais alors que les solutions sont de plus en plus nombreuses pour la gestion des données, comment choisir la bonne ?
Dans cet article, nous allons nous intéresser aux différentes solutions disponibles avec Next 15 et React 19. Ce sera l'occasion de découvrir quelques nouvelles fonctionnalités de React 19 comme l'API use nouvellement introduite, maintenant stable depuis quelques mois.
Quelques rappels
Ici, nous allons beaucoup parler des Client Components
, Server Components
, Server Actions
et de la manière dont l'ensemble s'articule pour servir et manipuler les données. Si vous n'êtes pas familier avec ces concepts, je vous invite à lire les articles Next 13.4 : Tour d'horizon des server actions et Focus sur le nouveau routeur app de Next 13. On notera que le volet sur les mutations de données n'a pas beaucoup évolué depuis la version 13, nous nous concentrerons donc sur la partie relative au data fetching.
Concepts-clés
Jusqu'à la version 15, il existait quatre manières de récupérer des données :
Client Components
fetch
: Récupérer des données directement côté client
Cette approche se fait en passant souvent par une librairie tierce comme React Query ou dans un hook useEffect
doublé d'une gestion d'état. Cela permet de récupérer des données directement côté client sans passer par le serveur. Cependant, cette méthode souffre de quelques limitations, notamment en termes de performances car les données ne seront récupérées que lorsque le composant sera monté, ce qui peut donner une impression de latence à l'utilisateur.
Pour rappel l'API fetch est surchargée par NextJS et dispose de fonctionnalités supplémentaires par rapport à l'API fetch native, comme la gestion du cache et de la revalidation des données.
server actions
: Appelées depuis le client, exécutées sur le serveur
Sensiblement identique à un fetch côté client, cette méthode offre une developer experience plus simple. Mais souffre des mêmes limitations que le fetch côté client. S'ajoute à cela le fait que les server actions
peuvent ouvrir la porte à des erreurs de sécurité si on se laisse bercer par leur implémentation facile et que l'on oublie de sécuriser ces fonctions - pour rappel, ce sont des endpoints crées automatiquement, c'est pour cette raison qu'il faut les protéger.
L'usage de server actions
ou de l'API fetch
sera déterminé par le contexte de l'application,
si vous exploitez une API externe, vous aurez besoin d'utiliser l'API fetch
côté client, au
contraire si vous avez un ORM comme Prisma ou
Drizzle, vous pourrez utiliser les server actions
qui pourront
utiliser vos fonctions de manipulation de données.
Server Components
fetch
: Récupération de données sur le serveur puis passage à l'UIserver function
: Exécutées sur le serveur puis passage à l'UI
☝️ Ici on précise que ce sont des server functions
car elles ne sont pas nécessairement des
server actions
(sans la directive use server
), ce qui évite la création d'un endpoint qui
serait non exploité par un Client Component.
Dans les deux cas, on parle de fonctions asynchrones, qui seront attendues par le serveur avant de passer à l'UI. Le rendu de la page sera donc bloqué tant que la fonction ne sera pas terminée.
Bilan des méthodes classiques
En fonction du besoin, on peut avoir besoin de récupérer des données côté client ou côté serveur. Dans le cas où le data fetching implique une interaction utilisateur préalable, il faudra utiliser un Client Component
et donc un fetch
ou une server action
, avec les limitations que cela implique sur les performances.
Si aucune interaction utilisateur n'est nécessaire, on peut utiliser un Server Component
et donc une server function
ou un fetch
côté serveur, avec l'inconvénient de bloquer le rendu de la page tant que les données ne sont pas récupérées.
Pour la partie récupération de données dynamiques, peu de choses ont changé depuis la version 13. Cependant, pour la partie récupération de données statiques, on a désormais une plus grande marge de manœuvre, principalement autour de l'expérience utilisateur, c'est ce que nous allons voir avec l'utilisation de l'API use
.
L'API use
et le passage de Promises
On vient de le voir, la récupération de données côté serveur comporte des limitations. Notamment en termes de performances, principalement due au fait que le rendu de la page est bloqué tant que les données ne sont pas récupérées car ces données sont récupérées de manière synchrone (utilisation du await
). Ce qui peut être problématique avec une grande quantité de données ou une latence importante.
Depuis l'apparition des Server Components, il est possible de passer des Promises
à un Client Composant, du moment où le résultat de la Promise
est une valeur sérialisable (pas de fonction donc). Cependant, jusqu'à présent, il n'était pas possible de bloquer le rendu du Client Component tant que la Promise
n'est pas résolue.
Pour répondre à ce problème, React 19 introduit une nouvelle API: use
. Nous allons voir comment cette nouvelle API permet de cibler le composant qui doit attendre la résolution de la Promise
, et d'afficher un fallback
jusqu'à ce que la Promise
soit résolue, voire une erreur si la Promise
est rejetée.
Optimiser l'expérience utilisateur
Comment fonctionne le passage de Promises avec Next ?
Pour bien comprendre le fonctionnement de l'API use
, intéressons-nous à la manière dont Next 15 gère le passage de Promises
à un Client Component.
Pour l'exemple, nous allons récupérer les données de l'ensemble des planètes du système solaire.
import { getPlanets, type Planet } from '@/lib/planets'
import Planets from './Planets'
export default function PlanetsPage() {
const planetsPromise: Promise<Planet[]> = getPlanets() // Récupération asynchrone des données
return <Planets planetsPromise={planetsPromise} />
}
Dans cet exemple, lorsque l'utilisateur navigue vers la page /planets
, le serveur Next va :
-
Faire un rendu de la page, non bloqué car la
Promise
est envoyée de manière asynchrone au client. -
Envoyer le HTML au client + le bundle JavaScript qui va permettre de construire la page (le Client Component), sans tenir compte de la
Promise
(en réalité la résolution de laPromise
a déjà commencé côté serveur).
Les réponses du serveur portent le header Transfer-Encoding: chunked pour indiquer que la réponse est découpée en plusieurs parties. À ce stade, la connexion est maintenue ouverte pour recevoir les futurs chunks.
-
Lorsque la
Promise
est résolue côté serveur, celui-ci envoie un dernier chunk sur la connexion maintenue ouverte jusqu'à présent. -
Le client va recevoir le dernier chunk et va pouvoir construire la page avec les données récupérées.
À ce stade, on voit déjà l'avantage majeur de cette approche qui permet d'exécuter une fonction asynchrone côté serveur sans bloquer le rendu de la page et sans utiliser de server actions
impliquant un nouvel appel HTTP.
⚠️ Notez que la fonction asynchrone sera exécutée même si elle n'est pas consommée par le Client
Component. Veillez à ne pas laisser de Promise
inutiles dans le code, cela pourrait avoir un
impact sur les performances.
Comment fonctionne l'API use
?
Visuellement, il n'y a pas beaucoup de changement... pour l'instant, car on n'exploite pas le fait d'avoir un rendu non-bloquant, mais c'est là que l'API use
intervient. Elle va nous permettre de cibler le composant qui doit attendre la résolution de la Promise
et d'afficher un fallback
en attendant ou une erreur si la Promise
est rejetée.
Si le composant PlanetsPage
contient des méta-données, comme un titre ou une description, ceux-ci seront affichés avant que la Promise
soit résolue. Idem pour les éventuels layouts ou autres éléments autres que le composant Planets
. Ce qui va rendre la page visuellement plus fluide pour l'utilisateur.
Pour profiter des avantages de l'API use
, nous allons donc wrapper le composant Planets
dans un Suspense
et/ou un ErrorBoundary
et utiliser use
dans le composant client Planets
.
import { Suspense } from 'react'
import { ErrorBoundary } from 'react-error-boundary'
import Planets from './Planets'
import { getPlanets, type Planet } from '@/lib/planets'
export default function PlanetsPage() {
const planetsPromise: Promise<Planet[]> = getPlanets() // Récupération asynchrone des données
return (
<ErrorBoundary fallback={<div>Error</div>}>
<Suspense fallback={<div>Loading...</div>}>
<Planets planetsPromise={planetsPromise} />
</Suspense>
</ErrorBoundary>
)
}
'use client'
import { use } from 'react'
export default function Planets({ planetsPromise }) {
const planets = use(planetsPromise)
return (
<ul>
{planets.map(planet => (
<li key={planet.id}>{planet.name}</li>
))}
</ul>
)
}
Ici, React va détecter que le composant Planets
utilise l'API use
sur une Promise
, il va remplacer le composant Planets
par le fallback du Suspense
dans l'arbre de composants et restaurer le composant Planets
lorsque la Promise
sera résolue, ou afficher l'erreur si la Promise
est rejetée.
Passage de Promises entre Client Components
L'objectif premier de cette nouvelle API est avant tout de pouvoir contrôler les états de chargement et d'erreur lors de la récupération de données, mais on peut aussi l'utiliser pour aller plus loin dans la granularité du data fetching et faire "descendre" notre état de chargement au plus proche des composants concernés. Parfois, cela va nous pousser à réorganiser nos composants pour les rendre plus petits et plus indépendants, mais au final cela nous permettra de gagner en lisibilité et maintenabilité en plus d'améliorer l'expérience utilisateur.
Autre cas d'utilisation
Parmi les autres cas d'utilisation de l'API use
, on peut citer :
- utilisable dans des
Server Components
- utilisable de manière conditionnelle
- utilisable pour récupérer des données d'un
Contexte
Inconvénients
Hélas, l'API use
nécessite de mettre en place un Suspense
et/ou un ErrorBoundary
pour chaque composant qui l'utilise. Cela peut devenir rapidement fastidieux à gérer, et donner une sensation de boilerplate.
Les fichiers loading.tsx
et error.tsx
peuvent être utilisés pour gérer les états de chargement
et d'erreur de manière globale ou par page. Ils ne répondent pas au même niveau de granularité
qu'un Suspense
ciblé sur un composant mais peuvent prévenir d'oublis potentiels.
Conclusion
Si vous souhaitez optimiser l'expérience utilisateur de vos pages, l'API use
est une solution à ne pas négliger, si vos fonctions asynchrones ne sont pas dépendantes d'une interaction utilisateur. Vous pourrez ainsi cibler les composants concernés par un état de loading ou d'erreur et éviter des états de "flash" qui pourraient survenir avec la gestion des états de chargement classique.
Autre avantage, et pas des moindres, c'est que désormais vos Server Components
pourront être rendus immédiatement sans attendre la résolution de la Promise
.