12k
All articles

Récupération de données dans les React Server Components

Récupérez des données dans les Server Components React avec des fonctions async, comprenez le cache Next.js 15/16 et évitez les waterfalls avec Promise.all.

OpenReplay Team
OpenReplay Team
Récupération de données dans les React Server Components

Dans un React Server Component, vous récupérez des données en transformant le composant en fonction async et en utilisant await directement dans le corps de la fonction — sans useEffect, sans état de chargement, et sans aller-retour API côté client. Il s’agit du modèle par défaut dans le App Router de Next.js, qui inverse deux habitudes héritées de React côté client : la récupération de données s’effectue désormais au moment du rendu, et le principal piège est la mise en cache, dont le comportement par défaut a changé dans Next.js 15. Cet article couvre le modèle actuel, la réalité de la mise en cache dans Next.js 16, pourquoi "use server" n’a rien à voir avec les Server Components, et comment éviter les cascades de requêtes.

Points clés

  • Dans un Server Component, vous récupérez des données en rendant le composant async et en attendant la requête avec await ; étant donné que les Server Components sont rendus côté serveur, les identifiants et la logique de requête ne sont pas inclus dans le bundle client, ce qui vous permet de requêter une base de données directement avec un ORM.
  • Par défaut, les requêtes fetch ne sont pas mises en cache dans les versions actuelles de Next.js — à l’inverse du comportement de Next.js 13/14 que décrivent encore de nombreux tutoriels.
  • Pour activer la mise en cache, vous devez l’activer explicitement : { cache: 'force-cache' } ou { next: { revalidate } } dans l’ancien modèle, ou la directive use cache une fois cacheComponents: true défini.
  • Il n’existe pas de directive pour les Server Components ; la directive “use server” est utilisée pour les Server Functions (Server Actions), une fonctionnalité distincte.
  • Lancez les requêtes indépendantes sans await, puis résolvez-les avec Promise.all pour éviter une cascade séquentielle.

Comment récupérer des données dans un Server Component ?

Le principe fondamental repose sur une seule opération : transformer le composant en fonction async et attendre la requête avec await. Les Server Components sont la valeur par défaut pour les layouts et les pages dans le App Router, donc aucune directive n’est nécessaire pour obtenir ce comportement — les composants async sont une fonctionnalité des Server Components qui permettent d’utiliser await lors du rendu.

// app/blog/page.tsx
export default async function Page() {
  const res = await fetch('https://api.vercel.app/blog')
  const posts = await res.json()
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

Étant donné que le code s’exécute uniquement côté serveur, vous pouvez contourner la couche API et interroger directement votre source de données. Vous pouvez effectuer des requêtes en base de données en toute sécurité à l’aide d’un ORM ou d’un client de base de données, tout en veillant à ce que les requêtes soient correctement authentifiées et autorisées.

// app/blog/page.tsx
import { db, posts } from '@/lib/db'

export default async function Page() {
  const allPosts = await db.select().from(posts)
  return (
    <ul>
      {allPosts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

Comparez cela au modèle client que vous remplacez : récupérer des données dans un useEffect, stocker le résultat dans un useState, et envoyer la logique de récupération (ainsi que d’éventuels secrets) vers le navigateur. Cette approche engendre un aller-retour client-serveur supplémentaire après le chargement de la page ; attendre la réponse côté serveur avec await l’élimine complètement.

fetch n’est pas mis en cache par défaut — le changement introduit dans Next.js 15/16

Dans les versions actuelles de Next.js, fetch n’est pas mis en cache par défaut — cela mérite d’être dit clairement, car la plupart des contenus plus anciens affirment le contraire. Selon la référence fetch actuelle de Next.js, le comportement par défaut est auto no cache : Next.js récupère la ressource depuis le serveur distant à chaque requête. L’ancien modèle de mise en cache se comportait de la même manière — par défaut, les requêtes fetch ne sont pas mises en cache, et vous mettez en cache une requête individuelle en définissant l’option cache à 'force-cache'. Dans Next.js 13/14, un fetch sans option était mis en cache (force-cache) par défaut, ce qui rend obsolète tout tutoriel s’appuyant sur ce comportement.

Pour activer la mise en cache avec l’ancien modèle, optez-y explicitement par requête :

// Mise en cache jusqu'à revalidation manuelle
await fetch('https://api.example.com/posts', { cache: 'force-cache' })

// Données servies depuis le cache, revalidées au maximum toutes les 3600s
await fetch('https://api.example.com/posts', { next: { revalidate: 3600 } })

Next.js 16 introduit également un second modèle, les Cache Components, dont l’activation se fait via la directive use cache. Le point qui déroute souvent les développeurs : use cache est une fonctionnalité des Cache Components ; pour l’activer, ajoutez l’option cacheComponents dans votre fichier next.config.ts. Sans ce flag, use cache n’a aucun effet et vous retombez sur les options fetch décrites ci-dessus.

// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = { cacheComponents: true }
export default nextConfig
import { cacheLife } from 'next/cache'

export async function getPosts() {
  'use cache'
  cacheLife('hours')
  const res = await fetch('https://api.example.com/posts')
  return res.json()
}

Ici, la directive use cache met en cache la valeur de retour des fonctions et composants async, et cacheLife() définit la durée. À noter que unstable_cache est remplacé par la directive use cache dans Next.js 16 — évitez de l’utiliser dans du nouveau code.

Cache Components DÉSACTIVÉ (ancien modèle)Cache Components ACTIVÉ (cacheComponents: true)
Comportement par défaut de fetchNon mis en cacheNon mis en cache
Mise en cache d’un fetch{ cache: 'force-cache' }Directive use cache
Revalidation temporelle{ next: { revalidate: n } }Profil cacheLife()
Mise en cache de données non-fetchReact.cache / config de routeuse cache sur la fonction

Deux comportements sont indépendants de la mise en cache. Premièrement, les requêtes fetch utilisant GET avec la même URL et les mêmes options sont automatiquement mémoïsées pendant un passage de rendu serveur ; ainsi, si vous appelez le même fetch dans plusieurs composants, Next.js ne l’exécute qu’une seule fois et partage le résultat. Deuxièmement, pour les sources non-fetch, encapsulez l’appel dans cache() de React ; React.cache est limité à la requête en cours uniquement — chaque requête dispose de sa propre portée de mémoïsation, sans partage entre les requêtes.

”use server” ne crée pas un Server Component

Si quelque chose déroute les nouveaux utilisateurs des RSC, c’est bien cette directive. Une idée reçue courante est que les Server Components sont identifiés par “use server”, mais il n’existe pas de directive pour les Server Components ; la directive “use server” est utilisée pour les Server Functions. Les Server Components sont simplement la valeur par défaut dans le App Router — un fichier sans directive en est déjà un. "use server" marque les Server Functions (Server Actions), et selon la référence React, les Server Functions sont conçues pour les mutations qui modifient l’état côté serveur ; elles ne sont pas recommandées pour la récupération de données. Utilisez des Server Components async classiques pour lire des données ; utilisez "use server" pour les écrire.

Éviter les cascades et streamer les données lentes

Attendre les requêtes les unes après les autres crée une cascade séquentielle. Au sein d’un même composant, plusieurs requêtes async/await peuvent rester séquentielles si elles sont placées l’une après l’autre ; lancez plusieurs requêtes en appelant fetch, puis attendez-les avec Promise.all. Appeler les fonctions sans await les démarre immédiatement :

export default async function Page({ params }: { params: Promise<{ username: string }> }) {
  const { username } = await params
  const artistData = getArtist(username)   // démarre immédiatement
  const albumsData = getAlbums(username)   // démarre immédiatement
  const [artist, albums] = await Promise.all([artistData, albumsData])
  return <h1>{artist.name}</h1>
}

Pour les données véritablement lentes, ne bloquez pas la route. Si certaines requêtes de données sont lentes, toute la route est bloquée jusqu’à ce que toutes les données soient récupérées ; pour améliorer le temps de chargement, découpez la page en fragments et envoyez-les progressivement au client. Encapsulez le sous-arbre lent dans <Suspense> (ou ajoutez un loading.js), et pour les données de moindre priorité, démarrez la promesse côté serveur et lisez-la côté client avec l’API use :

// Server Component — à noter : commentsPromise n'est PAS attendu avec await
import { Suspense } from 'react'
import Comments from './comments'

export default async function Page({ id }: { id: string }) {
  const commentsPromise = getComments(id)
  return (
    <Suspense fallback={<p>Chargement des commentaires…</p>}>
      <Comments commentsPromise={commentsPromise} />
    </Suspense>
  )
}
// Client Component
'use client'
import { use } from 'react'

export default function Comments({ commentsPromise }: { commentsPromise: Promise<Comment[]> }) {
  const comments = use(commentsPromise)
  return comments.map((c) => <p key={c.id}>{c.text}</p>)
}

Vous démarrez la promesse côté serveur et l’attendez côté client avec l’API use ; étant donné que les composants async ne sont pas pris en charge côté client, vous attendez la promesse avec use. Lorsque cela échoue, l’erreur n’est visible que là où se trouve l’utilisateur — un fallback Suspense qui ne se résout jamais en contenu, ou un fragment streamé qui arrive après l’hydratation — et un enregistrement de session est ce qui vous permet de reconstituer exactement ce que le navigateur a rendu à ce moment précis.

Garder les secrets côté serveur

Les données récupérées côté serveur transitent vers les Client Components sous forme de props, et ces props doivent être sérialisables — transmettez des données brutes, pas des fonctions ni des instances de classe. Les secrets restent en place par défaut car le code des Server Components n’est jamais envoyé au client, mais les modules partagés peuvent provoquer des fuites. Dans Next.js, seules les variables d’environnement préfixées par NEXT_PUBLIC_ sont incluses dans le bundle client ; si les variables ne sont pas préfixées, Next.js les remplace par une chaîne vide. Pour sécuriser un module contenant des secrets, importez le package server-only en tête de fichier afin qu’une importation accidentelle côté client échoue au moment du build. React Context ne peut pas non plus être utilisé dans un Server Component — encapsulez-le dans un provider 'use client' et rendez ce provider dans votre layout serveur.

Le changement de paradigme est minime mais complet : déplacez le fetch dans un composant async, attendez-le avec await, et traitez la mise en cache comme quelque chose que vous activez explicitement plutôt que comme quelque chose qui se produit automatiquement. Commencez par migrer un seul fetch dans un useEffect vers un Server Component async, vérifiez que la requête s’exécute côté serveur, puis décidez route par route si ces données doivent être mises en cache avec { next: { revalidate } } ou la directive use cache.

FAQ

fetch est-il mis en cache par défaut dans Next.js 16 ?

Non. Depuis Next.js 15 et dans la version 16, fetch n'est pas mis en cache par défaut et interroge le serveur d'origine à chaque requête. C'est l'inverse de Next.js 13/14, où un fetch sans option était mis en cache avec force-cache par défaut. Pour activer la mise en cache avec l'ancien modèle, vous devez l'activer explicitement par requête en définissant cache à force-cache ou en spécifiant une valeur next revalidate ; les tutoriels plus anciens affirmant que la mise en cache est activée par défaut sont obsolètes.

Quelle est la différence entre use cache et next revalidate pour la mise en cache d'un fetch ?

Ils appartiennent à deux modèles distincts. L'option next revalidate et force-cache fonctionnent avec l'ancien modèle de mise en cache, sans configuration supplémentaire. La directive use cache est une fonctionnalité des Cache Components qui ne prend effet que lorsque cacheComponents est défini à true dans next.config.ts, et sa durée est contrôlée avec cacheLife. Sans ce flag, use cache n'a aucun effet, ce qui signifie qu'un fetch que vous souhaitiez mettre en cache interroge silencieusement le serveur d'origine à chaque requête.

Puis-je utiliser useEffect pour récupérer des données dans un Server Component ?

Non. useEffect s'exécute uniquement dans le navigateur et ne peut donc pas s'exécuter dans un Server Component, qui est rendu uniquement côté serveur. Dans un Server Component, vous rendez le composant async et attendez la requête directement lors du rendu, sans état de chargement ni aller-retour client. Si vous avez besoin d'une récupération de données côté client, ajoutez la directive use client pour en faire un Client Component, ou démarrez la promesse côté serveur et lisez-la côté client avec l'API use de React.

La directive use server transforme-t-elle un composant en Server Component ?

Non. Il n'existe pas de directive pour les Server Components ; ils constituent simplement la valeur par défaut dans le App Router, donc un fichier sans directive en est déjà un. La directive use server marque les Server Functions, également appelées Server Actions, qui sont conçues pour les mutations modifiant l'état côté serveur et ne sont pas recommandées pour la récupération de données. Utilisez un Server Component async classique pour lire des données et use server pour les écrire.

Que se passe-t-il si une requête échoue lors de l'utilisation de Promise.all pour des requêtes parallèles ?

Promise.all rejette dès qu'une seule requête est rejetée, en ignorant les résultats des autres et en faisant échouer l'ensemble du rendu. Si une requête doit pouvoir échouer sans affecter les autres, utilisez plutôt Promise.allSettled, qui se résout avec le statut de chaque requête et vous permet de gérer les échecs individuellement. Lancez les fetches sans await pour les exécuter en parallèle, puis choisissez Promise.all ou Promise.allSettled selon la manière dont vous souhaitez gérer les échecs.

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.