Guide complet SWR : maîtriser les stratégies de cache et les mises à jour optimistes en pratique

Vous fixez l’écran, le spinner de chargement familier — pour la Nième fois que vous rechargez la liste des utilisateurs. Chaque changement d’onglet : chargement. Chaque retour depuis une autre page : chargement. Même après un coup de fil qui fait perdre le focus au navigateur, au retour c’est encore chargement. Le pire ? Ces données ont été chargées il y a une seconde.
Dans un projet React, chaque récupération de données implique useState, useEffect, plus la gestion du loading et des erreurs. Vingt lignes de code minimum. Pire encore : plusieurs composants ont besoin des mêmes données — soit vous remontez l’état au parent (encore plus de code), soit chaque composant fait sa propre requête (gaspillage de bande passante).
Quand j’ai découvert SWR, la sensation était comparable à la première fois que j’ai utilisé Git à la place de copier des dossiers à la main. Trois lignes de code, 80 % des problèmes réglés. Sans exagérer.
Aujourd’hui, parlons de SWR — la bibliothèque React de récupération de données de Vercel. Le principe central tient en un mot : stale-while-revalidate (obsolète – revalider). Ça sonne technique ? L’idée est simple : d’abord vous voyez la « vieille photo » en cache (rapide), en arrière-plan on en prend une nouvelle (à jour), et une fois prête on la remplace discrètement (sans à-coup).
Pourquoi SWR ? Trois points douloureux du fetch classique
Regardons à quoi ressemble la récupération de données classique en React. Supposons que vous vouliez afficher une liste d’utilisateurs :
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch('/api/users')
.then(res => res.json())
.then(data => {
setUsers(data);
setLoading(false);
})
.catch(err => {
setError(err);
setLoading(false);
});
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <ul>{users.map(user => <li key={user.id}>{user.name}</li>)}</ul>;
}
Vingt-quatre lignes de code — juste pour afficher une liste. Vous trouvez ça acceptable ? Pensez à ces scénarios :
Point douloureux 1 : du boilerplate à répétition
Chaque composant qui a besoin de données doit suivre le même schéma. Liste d’utilisateurs, liste d’articles, liste de commentaires — trois états, plein de if, gestion des erreurs… Le copier-coller devient embarrassant. Une fois, j’ai compté : dans un projet moyen, ce genre de snippets représentait environ 20 % du code.
Point douloureux 2 : le cache ? Inexistant
Le plus frustrant : pas de cache. L’utilisateur va de l’accueil à une page de détail, puis revient — encore un long chargement. Les données datent de trente secondes, et pourtant une nouvelle requête part. Les utilisateurs disent « votre site est lent » — souvent ce n’est pas l’API qui est lente, c’est l’absence totale de cache.
On peut construire une gestion d’état globale — Redux, Zustand, store, actions, reducers… Une fonction qui prendrait trois minutes devient trente minutes d’architecture.
Point douloureux 3 : synchroniser les données entre composants
Plusieurs composants, mêmes données : messages non lus dans l’en-tête, la barre latérale et la page des messages. Remonter l’état jusqu’en haut ? Des props sur dix niveaux. Context ? Chaque mise à jour re-rend tout l’arbre.
Une fois, j’ai passé deux jours sur une fonctionnalité de notification pourtant simple — au commit, le diff ne montrait que setState et useEffect. J’avais honte moi-même.
Voilà pourquoi SWR est populaire. Ce n’est pas un énième outil — il résout de vrais problèmes. Le même code avec SWR :
import useSWR from 'swr';
function UserList() {
const { data, error, isLoading } = useSWR('/api/users', fetcher);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error!</div>;
return <ul>{data.map(user => <li key={user.id}>{user.name}</li>)}</ul>;
}
Neuf lignes. Terminé. Avec cache, revalidation automatique et partage entre composants. Le fetcher est votre fonction fetch — en général définie une fois globalement :
const fetcher = url => fetch(url).then(r => r.json());
La différence parle d’elle-même.
Concept central : la stratégie Stale-While-Revalidate
Le nom SWR vient de la stratégie de cache HTTP définie dans la RFC 5861. Pas de panique devant « RFC » — le principe est simple.
Analogie avec une photo : vous voulez une photo récente d’un ami. Méthode classique — l’appeler, attendre qu’il en prenne une et vous l’envoie. Avec SWR — d’abord sortir la vieille photo de votre dernière rencontre (peut-être la semaine dernière), la regarder en lui envoyant un message pour en prendre une nouvelle, puis remplacer quand elle arrive.
La différence : vous n’attendez pas les mains vides. Contenu immédiat (peut-être un peu daté), tout en garantissant la fraîcheur finale. C’est stale-while-revalidate — montrer l’obsolète, revalider en parallèle.
Flux complet de SWR :
-
Étape 1 : retourner le cache immédiatement (stale)
- Au montage, SWR vérifie le cache local
- Cache présent ? Retour immédiat, page instantanée
- Pas de cache ?
undefined, affichage du loading
-
Étape 2 : requête en arrière-plan (revalidate)
- Avec ou sans cache : requête API
- L’utilisateur ne le remarque pas, le contenu est déjà là
-
Étape 3 : mise à jour des données
- Après la réponse : cache et UI mis à jour discrètement
- Si les données changent : React re-rend
- Si rien ne change : rien ne se passe
Exemple concret — affichage d’un cours boursier :
function StockPrice({ symbol }) {
const { data, error } = useSWR(`/api/stock/${symbol}`, fetcher);
return (
<div>
<h2>{symbol}</h2>
<p>Prix : {data ? `$${data.price}` : 'Loading...'}</p>
<span>Mis à jour : {data?.updatedAt}</span>
</div>
);
}
À la première ouverture, data est undefined — « Loading… ». Une seconde plus tard, le prix s’affiche.
Changement d’onglet, dix minutes de mails, retour — prix instantané depuis le cache, pas de flash de loading. En parallèle, SWR lance une nouvelle requête. Cours modifié → UI mise à jour ; inchangé → ça reste.
Trois moments de revalidation automatique par défaut :
- Composant remonté (
revalidateOnMount) : rechargement ou retour de route - Fenêtre de nouveau au focus (
revalidateOnFocus) : retour à l’onglet du navigateur - Réseau rétabli (
revalidateOnReconnect) : après une coupure
Vous n’avez rien à configurer — SWR garde les données fraîches. Une demi-heure de mails, retour sur votre site → refresh automatique. Métro sans réseau, retour en 4G → rechargement automatique.
Quand j’ai découvert que c’était le comportement par défaut, j’étais surpris. Avant : soit des données périmées, soit écouter manuellement visibilitychange, online — beaucoup de code. Maintenant : offert.
Le concept de key :
Le premier paramètre de useSWR est une chaîne comme '/api/users' — la key, centrale.
// Deux composants, même key
function Header() {
const { data } = useSWR('/api/user', fetcher);
return <div>Bienvenue, {data?.name}</div>;
}
function Profile() {
const { data } = useSWR('/api/user', fetcher);
return <div>Profil : {data?.email}</div>;
}
Même key → données partagées. Un seul appel API, les deux composants synchronisés.
Pas de gestion d’état, pas de Context — juste la key.
Stratégies de cache en détail : rendre le fetch plus intelligent
SWR met en cache et revalide automatiquement — en pratique, différentes données exigent une « fraîcheur » différente. L’avatar change rarement, le cours boursier chaque seconde. Il faut ajuster la stratégie.
Le comportement par défaut est malin, mais pas universel
SWR est assez agressif par défaut :
- Revalidation à chaque montage
- Revalidation au focus
- Revalidation à la reconnexion
Parfait pour le temps réel (chat, statut en ligne). Pour des données stables (liste d’articles, profil), c’est parfois excessif — onglet aller-retour, requête à chaque fois ? Inutile.
Options de configuration essentielles
const { data } = useSWR('/api/articles', fetcher, {
revalidateOnFocus: false, // pas de requête au focus
revalidateOnReconnect: false, // pas de requête à la reconnexion
refreshInterval: 0, // intervalle de polling en ms, 0 = désactivé
dedupingInterval: 2000, // dédupliquer les requêtes identiques sur 2 s
});
Les noms sont explicites.
Scénario 1 : données temps réel (actions, compteur en ligne)
const { data } = useSWR('/api/stock/AAPL', fetcher, {
refreshInterval: 1000, // polling chaque seconde
revalidateOnFocus: true // mise à jour immédiate au retour
});
Fraîcheur maximale — le polling est simple (WebSocket serait mieux, autre sujet).
Scénario 2 : données relativement stables (profil, liste d’articles)
const { data } = useSWR('/api/profile', fetcher, {
revalidateOnFocus: false,
refreshInterval: 0,
dedupingInterval: 60000, // pas de doublon pendant 1 minute
});
Le profil change rarement — changement d’onglet sans refresh. Après une modification manuelle, vous pouvez déclencher une mise à jour avec mutate (plus loin).
Scénario 3 : contenu quasi statique (documentation, aide)
const { data } = useSWR('/api/docs', fetcher, {
revalidateOnFocus: false,
revalidateOnReconnect: false,
revalidateOnMount: false,
revalidateIfStale: false,
});
Données qui peuvent rester un mois sans changer — après le premier chargement, cache quasi permanent jusqu’au rechargement manuel.
Configuration globale vs locale
Stratégie uniforme dans toute l’app avec SWRConfig :
import { SWRConfig } from 'swr';
function App() {
return (
<SWRConfig value={{
refreshInterval: 3000,
fetcher: (url) => fetch(url).then(r => r.json()),
revalidateOnFocus: false,
}}>
<Dashboard />
</SWRConfig>
);
}
Tous les useSWR enfants héritent ces valeurs ; un Hook peut surcharger :
// revalidateOnFocus: true écrase le false global
const { data } = useSWR('/api/realtime', fetcher, {
revalidateOnFocus: true
});
Déduplication des requêtes : économiser votre quota API
Trois composants montent en même temps avec useSWR('/api/user') — SWR n’envoie qu’une requête, tous partagent le résultat.
Fenêtre par défaut : 2 secondes (dedupingInterval: 2000). Si vos composants se remontent vite (changement d’onglet rapide), augmentez cette valeur.
Cette fonctionnalité m’a sauvé un quota API : un défilement rapide déclenchait la même requête en rafale — avec la déduplication, calme immédiat. Symptôme corrigé, mais corrigez aussi la cause dans le code.
Fetch conditionnel : requêtes dépendantes
D’abord A, puis B — une key à null met en pause :
const { data: user } = useSWR('/api/user', fetcher);
const { data: projects } = useSWR(
user ? `/api/projects?userId=${user.id}` : null,
fetcher
);
Sans user, pas de requête ; dès que user est là, projects démarre. Dépendance séquentielle, effort minimal.
Mises à jour optimistes : l’arme secrète pour une meilleure UX
Le cache règle la vitesse — pour les actions utilisateur, une question reste : afficher le cœur « J’aime » après la réponse API, ou immédiatement ?
Mise à jour optimiste (Optimistic Update) : supposer le succès, mettre à jour l’UI tout de suite, revenir en arrière en cas d’échec. Ça semble risqué — la plupart des actions réussissent, le gain UX est visible.
Classique vs optimiste
Classique :
- Clic sur « J’aime »
- Bouton en loading ou désactivé
- Attente 500 ms–2 s
- Cœur qui s’allume
- Utilisateur : « Ce site est lent… »
Optimiste :
- Clic
- Cœur immédiat
- Requête en arrière-plan
- Souvent succès — rien de plus
- Parfois échec — cœur gris, message d’erreur
Latence ressentie : 0 ms.
mutate : contrôler le cache manuellement
import { mutate } from 'swr';
// Déclencher la revalidation de /api/user
mutate('/api/user');
Pour l’optimisme, il faut plus — exemple Todo complet :
import useSWR, { mutate } from 'swr';
function TodoList() {
const { data: todos } = useSWR('/api/todos', fetcher);
const addTodo = async (text) => {
const newTodo = { id: Date.now(), text, completed: false };
mutate(
'/api/todos',
async (currentTodos) => {
const optimisticData = [...currentTodos, newTodo];
const savedTodo = await fetch('/api/todos', {
method: 'POST',
body: JSON.stringify(newTodo)
}).then(r => r.json());
return [...currentTodos, savedTodo];
},
{
optimisticData: [...todos, newTodo],
rollbackOnError: true,
revalidate: false,
}
);
};
return (
<div>
{todos?.map(todo => <div key={todo.id}>{todo.text}</div>)}
<button onClick={() => addTodo('New task')}>Add</button>
</div>
);
}
Flux : clic → nouveau Todo immédiat (optimisticData) → POST → succès : données serveur (id, timestamps) → échec : rollback (rollbackOnError: true).
Quatre options centrales
1. optimisticData — affichage immédiat
optimisticData: [...todos, newTodo]
Ou sous forme de fonction :
optimisticData: (currentTodos) => [...currentTodos, newTodo]
2. populateCache — mettre à jour le cache avec la valeur de retour ?
Par défaut true. Le serveur ajoute souvent id, createdAt — alors utile. Réponse API incomplète → false.
3. revalidate — refetch ?
En général false — vous avez mis à jour manuellement. Seulement si vous voulez que SWR confirme.
4. rollbackOnError — revenir en arrière en cas d’erreur ?
Important : erreur dans mutate → retour automatique à l’état précédent. Cœur allumé, API en échec → cœur éteint.
Contrôle plus fin :
rollbackOnError: (error) => {
return error.name !== 'AbortError';
}
Pratique : supprimer un Todo
const deleteTodo = async (id) => {
mutate(
'/api/todos',
async (currentTodos) => {
const optimistic = currentTodos.filter(t => t.id !== id);
await fetch(`/api/todos/${id}`, { method: 'DELETE' });
return optimistic;
},
{
optimisticData: todos.filter(t => t.id !== id),
rollbackOnError: true,
}
);
};
Clic → Todo disparaît. Erreur serveur → Todo revient, message d’erreur.
useSWRMutation — une syntaxe plus élégante
Depuis SWR 2.0 :
import useSWRMutation from 'swr/mutation';
async function updateUser(url, { arg }) {
await fetch(url, {
method: 'POST',
body: JSON.stringify(arg)
});
}
function Profile() {
const { trigger, isMutating } = useSWRMutation('/api/user', updateUser);
return (
<button
onClick={() => trigger({ name: 'John' })}
disabled={isMutating}
>
Update Name
</button>
);
}
trigger lance la mutation, isMutating remplace le loading manuel.
Quand utiliser l’optimisme ?
✅ Adapté :
- Like/favori (peu d’échecs, réversible)
- Ajout/suppression dans une liste
- Toggle (notifications)
- Sauvegarde de brouillon
❌ Inadapté :
- Paiement (attendre la confirmation)
- Suppression de compte (irréversible)
- Mot de passe, permissions
- Résultats calculés côté serveur (rapport)
Règle : faible taux d’échec, réversible, UX prioritaire → optimiste. Critique ou irréversible → attendre le serveur.
Une erreur vécue : commentaires avec optimisme — réseau lent, l’utilisateur clique « Envoyer » plusieurs fois, dizaines d’entrées identiques dans l’UI. Solution : isMutating, bouton désactivé pendant la requête. Optimisme oui — mais prévoir les cas limites.
SWR vs React Query : comment choisir ?
Les deux sont excellents et activement maintenus en 2025. Un mauvais choix est rarement fatal — le bon choix simplifie le développement.
Taille : SWR plus léger
- SWR : 5,3 Ko (gzip)
- React Query (TanStack Query) : 16,2 Ko
Avec un budget bundle strict (landing page, H5 mobile), SWR compte. Dans beaucoup d’apps, ~10 Ko sont négligeables.
Complexité : SWR plus simple
API centrale : un useSWR. Compris en quelques minutes.
React Query : QueryClient, useQuery, useMutation, queryKeys, cache time vs stale time — courbe d’apprentissage plus raide, documentation longue.
Équipes moins expérimentées → souvent SWR.
Fonctionnalités : React Query plus complet
- DevTools officielles — queries, cache, refetch visibles. SWR sans DevTools officielles.
- Contrôle de cache plus fin — cache time et stale time séparés ; SWR : cache + revalidate.
- Pagination/infinite —
useInfiniteQueryvsuseSWRInfinite, React Query un peu plus mature. - Mutations —
useMutationavec état global, retry, callbacks ;useSWRMutationdepuis 2.0, plus léger.
Communauté
TanStack Query : communauté plus large, plus de réponses sur Stack Overflow.
SWR : Vercel, intégration Next.js étroite — avec Next.js, choix naturel.
Recommandation
| SWR | React Query |
|---|---|
| Logique de données simple | Besoins de cache complexes |
| Bundle sensible | +10 Ko acceptable |
| Next.js | CRA, Vite, autres |
| Peu d’expérience frontend | Équipe qui aime les outils riches |
| Démarrage rapide | Temps pour l’écosystème |
Mon expérience : MVP → SWR. Complexité croissante → éventuellement React Query. La plupart du temps, SWR suffit.
La migration reste raisonnable — cache, revalidate, mutation sont proches. Ne sur-analysez pas.
Next.js et SWR : une combinaison naturelle
Tous deux de Vercel — l’intégration est fluide.
App Router
Les Server Components sont la norme ; SWR est côté client :
'use client';
import useSWR from 'swr';
export default function Profile() {
const { data } = useSWR('/api/user', fetcher);
return <div>{data?.name}</div>;
}
N’oubliez pas 'use client'.
SSR + fallback
Données initiales depuis getStaticProps / getServerSideProps comme fallback :
// pages/profile.js
export async function getStaticProps() {
const user = await fetch('https://api.example.com/user').then(r => r.json());
return {
props: {
fallback: {
'/api/user': user
}
},
revalidate: 60
}
}
export default function Profile({ fallback }) {
return (
<SWRConfig value={{ fallback }}>
<UserProfile />
</SWRConfig>
);
}
function UserProfile() {
const { data } = useSWR('/api/user', fetcher);
return <div>{data.name}</div>;
}
Première visite : contenu complet (SEO), puis revalidation SWR côté client.
Prefetch
import { mutate } from 'swr';
function ArticleLink({ id }) {
const prefetch = () => {
mutate(`/api/article/${id}`, fetch(`/api/article/${id}`).then(r => r.json()));
};
return (
<Link href={`/article/${id}`} onMouseEnter={prefetch}>
Read more
</Link>
);
}
Le survol charge déjà — le clic sort souvent du cache.
Défilement infini : useSWRInfinite
import useSWRInfinite from 'swr/infinite';
function ArticleList() {
const getKey = (pageIndex, previousPageData) => {
if (previousPageData && !previousPageData.length) return null;
return `/api/articles?page=${pageIndex + 1}&limit=10`;
};
const { data, size, setSize, isLoading } = useSWRInfinite(getKey, fetcher);
const articles = data ? data.flat() : [];
const isLoadingMore = isLoading || (size > 0 && data && typeof data[size - 1] === 'undefined');
return (
<div>
{articles.map(article => (
<div key={article.id}>{article.title}</div>
))}
<button
onClick={() => setSize(size + 1)}
disabled={isLoadingMore}
>
{isLoadingMore ? 'Loading...' : 'Load More'}
</button>
</div>
);
}
setSize(size + 1) charge la page suivante ; chaque page reste en cache.
Performance avec Next.js
-
Fréquence de revalidation
- Contenu statique : désactiver la revalidation auto
- Données utilisateur : garder la revalidation au focus
- Temps réel :
refreshInterval
-
Cache des routes API
Cache-Controlsur les routes API- SWR respecte les en-têtes HTTP de cache
-
Éviter les cascades de requêtes
- Données dépendantes : fusionner côté serveur si possible
- Ou fetch parallèle dans Next.js
Cas réel : blog avec SWR + ISR — HTML statique instantané, en arrière-plan vérification des nouveaux articles. Rapide et SEO-friendly.
Performance statique plus fraîcheur dynamique — c’est Next.js + SWR.
Conclusion
SWR adresse trois problèmes : boilerplate, absence de cache, synchronisation entre composants. stale-while-revalidate est rapide et à jour. Revalidation, déduplication, optimisme couvrent la plupart des cas.
Bonnes pratiques :
- Adapter le cache au type de données — temps réel en polling, stable en cache long
- Optimisme pour les actions réversibles — faible taux d’échec requis
- Partager via les keys — pas de Redux juste pour des données API partagées
- Next.js : fallback pour le SSR
- Simple → SWR, complexe → React Query — pas de sur-ingénierie
SWR n’est pas une baguette magique — il ne corrige pas une mauvaise API ni une mauvaise architecture. Avec une API solide, le code frontend devient nettement plus élégant.
Essayez SWR sur votre prochain projet — vous verrez la différence.
Workflow complet SWR
De l'installation à la configuration, l'utilisation et l'optimisation du cache
⏱️ Estimated time: 1 hr
- 1
Step 1: Installation et bases
Installation :
```bash
npm install swr
```
Utilisation de base :
```tsx
'use client'
import useSWR from 'swr'
const fetcher = (url: string) => fetch(url).then(r => r.json())
export function UserList() {
const { data, error, isLoading } = useSWR('/api/users', fetcher)
if (error) return <div>Failed to load</div>
if (isLoading) return <div>Loading...</div>
return <div>{data.map(user => <div key={user.id}>{user.name}</div>)}</div>
}
```
Points clés :
• Marquer la composante avec 'use client'
• fetcher pour la récupération de données
• useSWR retourne data, error, isLoading - 2
Step 2: Configurer les options globales
Avec SWRConfig :
```tsx
'use client'
import { SWRConfig } from 'swr'
const fetcher = (url: string) => fetch(url).then(r => r.json())
export function Providers({ children }) {
return (
<SWRConfig
value={{
fetcher,
revalidateOnFocus: true,
revalidateOnReconnect: true,
refreshInterval: 0,
}}
>
{children}
</SWRConfig>
)
}
```
Options courantes :
• revalidateOnFocus : revalidation au focus de la fenêtre
• revalidateOnReconnect : revalidation à la reconnexion réseau
• refreshInterval : rafraîchissement périodique (0 = désactivé)
• dedupingInterval : intervalle de déduplication (2000 ms par défaut)
Point clé : configurer à la racine — tous les composants enfants héritent - 3
Step 3: Intégration Next.js (SSR)
Avec fallback data :
```tsx
// app/users/page.tsx (Server Component)
import { getUsers } from '@/lib/users'
export default async function UsersPage() {
const initialUsers = await getUsers()
return <UsersList initialUsers={initialUsers} />
}
// components/UsersList.tsx (Client Component)
'use client'
import useSWR from 'swr'
export function UsersList({ initialUsers }) {
const { data } = useSWR('/api/users', fetcher, {
fallbackData: initialUsers
})
return <div>{data.map(...)}</div>
}
```
Avantages :
• Premier écran depuis le SSR — chargement rapide
• Mises à jour via SWR — bonne UX
• SSR et cache client combinés
Point clé : utiliser fallbackData et non initialData (fallbackData ne déclenche pas de revalidation) - 4
Step 4: Mises à jour optimistes
Avec mutate :
```tsx
import { mutate } from 'swr'
async function updateUser(id: string, name: string) {
mutate(`/api/users/${id}`, { ...user, name }, false)
await fetch(`/api/users/${id}`, {
method: 'PATCH',
body: JSON.stringify({ name })
})
mutate(`/api/users/${id}`)
}
```
Points clés :
• Le troisième paramètre false = pas de revalidation
• Après succès, mutate à nouveau pour confirmer
• L'utilisateur voit la mise à jour immédiatement
FAQ
Qu'est-ce que SWR et pourquoi en avez-vous besoin ?
Points douloureux du fetch classique :
• useState, useEffect, loading et error par requête
• Vingt lignes minimum
• Données partagées : remontée d'état ou requêtes dupliquées
Avantages de SWR :
• Cache automatique via les mêmes keys
• Revalidation au focus et à la reconnexion
• Déduplication des requêtes
• Retry automatique en cas d'erreur
• Un Hook pour simplifier jusqu'à 90 % du code
Trois lignes pour 80 % des cas :
```tsx
const { data, error, isLoading } = useSWR('/api/users', fetcher)
```
Quelle est la différence entre SWR et React Query ?
• Léger, faible courbe d'apprentissage
• Convient à la plupart des projets
• Moins de fonctionnalités
• Vercel, bonne intégration Next.js
React Query :
• Plus puissant, plus de fonctionnalités
• Scénarios complexes
• Courbe d'apprentissage plus élevée
• Plus flexible, plus de configuration
Recommandation :
• Projet simple → SWR
• Complexe → React Query
• Incertain → SWR d'abord, migrer si besoin
Point clé : pas de sur-ingénierie — simple avec SWR, complexe avec React Query.
Comment utiliser SWR avec Next.js ?
```tsx
// app/users/page.tsx (Server Component)
export default async function UsersPage() {
const initialUsers = await getUsers()
return <UsersList initialUsers={initialUsers} />
}
// components/UsersList.tsx (Client Component)
'use client'
export function UsersList({ initialUsers }) {
const { data } = useSWR('/api/users', fetcher, {
fallbackData: initialUsers
})
return <div>{data.map(...)}</div>
}
```
Avantages :
• Premier écran depuis le SSR
• Mises à jour client via SWR
• SSR et cache client combinés
Points clés :
• fallbackData et non initialData
• fallbackData ne déclenche pas de revalidation
• Adapté à l'App Router
Quelle est la stratégie de cache de SWR ?
Flux :
1. Première requête : loading
2. Succès : affichage et mise en cache
3. Accès suivant : cache immédiat (rapide)
4. Arrière-plan : revalidation (à jour)
5. Terminé : mise à jour discrète (sans à-coup)
Caractéristiques :
• Cache partagé via les keys
• Déduplication
• Revalidation au focus/reconnexion
• Retry en cas d'erreur
Options :
• revalidateOnFocus
• revalidateOnReconnect
• refreshInterval
• dedupingInterval
L'utilisateur voit rarement le loading, les données restent fraîches.
Comment implémenter les mises à jour optimistes ?
```tsx
import { mutate } from 'swr'
async function updateUser(id: string, name: string) {
mutate(`/api/users/${id}`, { ...user, name }, false)
await fetch(`/api/users/${id}`, {
method: 'PATCH',
body: JSON.stringify({ name })
})
mutate(`/api/users/${id}`)
}
```
Points clés :
• Troisième paramètre false = pas de revalidation
• Après succès, mutate à nouveau
• Mise à jour UI immédiate
En cas d'échec : revenir aux données d'origine.
Pour quels scénarios SWR est-il adapté ?
• Données fréquemment mises à jour (listes, notifications)
• Plusieurs composants, mêmes données
• Cache et revalidation automatiques
• Projets Next.js
Moins adapté :
• Données statiques uniques
• Une seule requête sans revalidation
• Besoins de cache très complexes → React Query
Recommandation :
• La plupart des projets → SWR
• Complexe → React Query
• Très simple → fetch direct
Point clé : n'utilisez pas SWR pour SWR — choisissez selon le besoin réel.
14 min de lecture · Publié le: 19 déc. 2025 · Mis à jour le: 27 juil. 2026
Guide complet Next.js
Si vous arrivez depuis la recherche, le plus rapide est de passer à l’article précédent ou suivant de cette série.
Précédent
JWT ou Session ? Arrêtez de tergiverser — après cet article, vous saurez quoi choisir
Hésitez entre JWT et Session ? Comparaison pratique des deux stratégies de session : config NextAuth.js, performance et sécurité pour prendre la bonne décision.
Partie 49 sur 51
Suivant
Guide pratique Drizzle ORM : un ORM TypeScript 90 % plus léger que Prisma
Drizzle ORM ne pèse que 7,4 ko, soit 90 % de moins que Prisma, avec un cold start 73 % plus rapide. Configuration Next.js + Drizzle, API SQL-like et comparaison de performances avec Prisma pour choisir le bon ORM TypeScript.
Partie 51 sur 51



Commentaires
Connectez-vous avec GitHub pour laisser un commentaire