Prérequis
Avant de commencer, assurez-vous de disposer de :
- Node.js 20 ou version ultérieure
- Un projet Next.js 15+ utilisant l'App Router
- Une connaissance basique des hooks React et du cycle de rendu des composants
Ce que vous allez construire
À la fin de ce tutoriel, vous aurez :
- Intégré React Scan dans votre environnement de développement Next.js
- Un modèle mental clair pour lire la couche visuelle superposée
- Corrigé au moins une régression de performance réelle avec React Compiler ou la mémoïsation manuelle
- Un moniteur
onRenderréutilisable que vous pouvez glisser dans n'importe quel projet
Qu'est-ce que React Scan ?
React Scan est un outil de diagnostic de performance zéro-config pour les applications React. Il enveloppe le réconciliateur React et met en évidence chaque composant qui se re-rend à l'écran — sans plugin Babel, sans session de Profiler, sans devinettes.
Chaque surbrillance vous indique :
- Contour orange — le composant a rendu lors de ce cycle
- Contour gris — le composant a rendu mais n'a produit aucun changement dans le DOM (re-rendu inutile)
- Badge de comptage — le nombre de fois que le composant a rendu depuis le chargement de la page
Contrairement au React DevTools Profiler, React Scan est toujours actif pendant le développement. Vous voyez les re-rendus au moment où ils se produisent, sans avoir à démarrer et arrêter un enregistrement.
Étape 1 : Installer React Scan
Ajoutez le paquet en tant que dépendance de développement :
npm install react-scan --save-dev
# ou
pnpm add -D react-scanÉtape 2 : Créer un composant ReactScanProvider
Dans le Next.js App Router, le layout racine est un Server Component — vous ne pouvez pas appeler scan() au niveau du module. Utilisez plutôt le hook useScan dans un Client Component dédié.
Créez components/ReactScanProvider.tsx :
"use client";
import { useScan } from "react-scan";
export function ReactScanProvider() {
useScan({
enabled: process.env.NODE_ENV === "development",
log: false,
showToolbar: true,
animationSpeed: "fast",
trackUnnecessaryRenders: true,
showFPS: true,
showNotificationCount: true,
});
return null;
}useScan initialise le scanner uniquement côté client, ce qui le rend sûr dans un contexte SSR et n'affecte jamais le HTML rendu côté serveur.
Étape 3 : Monter le Provider dans le Layout Racine
Ouvrez app/layout.tsx et ajoutez ReactScanProvider comme premier enfant de <body> :
import { ReactScanProvider } from "@/components/ReactScanProvider";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="fr">
<body>
<ReactScanProvider />
{children}
</body>
</html>
);
}Démarrez le serveur de développement avec npm run dev et ouvrez votre navigateur. Vous devriez voir une barre d'outils flottante en bas à droite et des flashs oranges sur les composants qui se re-rendent.
Étape 4 : Lire la Couche Visuelle
Surbrillances oranges
Une surbrillance orange apparaît chaque fois que la fonction de rendu d'un composant s'exécute. C'est normal et attendu — l'objectif n'est pas d'éliminer tous les re-rendus, seulement les inutiles.
Surbrillances grises
Une surbrillance grise signifie que le composant a rendu mais a produit un résultat identique au rendu précédent. React Scan appelle ces re-rendus inutiles. Ils consomment du temps CPU sans mettre à jour l'interface utilisateur, et constituent la principale cible d'optimisation.
La barre d'outils flottante
La barre d'outils affiche :
- Compteur FPS — images par seconde ; tout ce qui est en dessous de 30 indique une interface saccadée
- Badge de notification — nombre de re-rendus inutiles détectés depuis le chargement de la page
Étape 5 : Reproduire un Anti-Modèle Courant
Construisons un exemple réaliste. Créez app/dashboard/page.tsx :
import { UserCard } from "@/components/UserCard";
import { StatsPanel } from "@/components/StatsPanel";
export default function DashboardPage() {
return (
<main>
<UserCard user={{ name: "Alice", role: "admin" }} />
<StatsPanel config={{ refreshInterval: 5000 }} />
</main>
);
}Créez components/UserCard.tsx :
"use client";
import { useState } from "react";
type User = { name: string; role: string };
export function UserCard({ user }: { user: User }) {
const [count, setCount] = useState(0);
return (
<div>
<p>{user.name} — {user.role}</p>
<button onClick={() => setCount((c) => c + 1)}>
Cliqué {count} fois
</button>
</div>
);
}Et components/StatsPanel.tsx :
"use client";
type Config = { refreshInterval: number };
export function StatsPanel({ config }: { config: Config }) {
return <div>Rafraîchit toutes les {config.refreshInterval}ms</div>;
}Cliquez plusieurs fois sur le bouton. React Scan met en évidence UserCard en orange à chaque clic — attendu, car l'état local a changé. Mais StatsPanel clignote en gris : il se re-rend à chaque re-rendu du parent même si les valeurs dans config n'ont pas changé. Le coupable est le littéral objet inline { refreshInterval: 5000 } — JavaScript crée une nouvelle référence à chaque cycle de rendu.
Étape 6 : Corriger les Re-rendus Inutiles
Option A — useMemo (manuel)
Stabilisez la référence dans DashboardPage :
"use client";
import { useMemo } from "react";
import { UserCard } from "@/components/UserCard";
import { StatsPanel } from "@/components/StatsPanel";
export default function DashboardPage() {
const statsConfig = useMemo(() => ({ refreshInterval: 5000 }), []);
return (
<main>
<UserCard user={{ name: "Alice", role: "admin" }} />
<StatsPanel config={statsConfig} />
</main>
);
}Rechargez la page. La surbrillance grise sur StatsPanel a disparu.
Option B — React Compiler (automatique, recommandé)
Si votre projet utilise React Compiler 1.0 (stable depuis octobre 2025, éprouvé chez Meta), vous n'avez pas besoin de useMemo du tout. Le compilateur détecte les valeurs stables et les mémoïse automatiquement lors de la compilation.
Activez-le dans next.config.ts pour Next.js 16 :
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
reactCompiler: true,
};
export default nextConfig;Reconstruisez et rouvrez le navigateur. Les surbrillances grises sur StatsPanel disparaissent — sans toucher au code de votre composant.
Étape 7 : Surveillance Programmatique avec onRender
Pour des budgets de performance automatisés, utilisez le callback onRender pour journaliser ou asserter sur les comptes de rendu :
"use client";
import { useScan } from "react-scan";
import type { Fiber, Render } from "react-scan";
export function ReactScanProvider() {
useScan({
enabled: process.env.NODE_ENV === "development",
log: false,
showToolbar: true,
trackUnnecessaryRenders: true,
onRender: (fiber: Fiber, renders: Array<Render>) => {
const componentName =
(fiber.type as { displayName?: string; name?: string })
?.displayName ??
(fiber.type as { name?: string })?.name ??
"Anonyme";
if (renders.length > 5) {
console.warn(
`[react-scan] ${componentName} a rendu ${renders.length} fois dans un seul commit`
);
}
},
});
return null;
}Cela journalise un avertissement chaque fois qu'un composant se re-rend plus de 5 fois dans un seul cycle de commit — un signal utile lors de la revue de code.
Étape 8 : Activation via Paramètre URL pour le Staging
Pour partager une URL de débogage avec un collègue sans activer React Scan pour tout le monde en staging, mettez à jour ReactScanProvider pour respecter un paramètre ?scan=true :
"use client";
import { useScan } from "react-scan";
function isScanEnabled() {
if (process.env.NODE_ENV === "development") return true;
if (typeof window === "undefined") return false;
return new URLSearchParams(window.location.search).get("scan") === "true";
}
export function ReactScanProvider() {
useScan({
enabled: isScanEnabled(),
showToolbar: true,
trackUnnecessaryRenders: true,
});
return null;
}Ajoutez ?scan=true à n'importe quelle URL de votre environnement de staging pour activer la couche visuelle à la demande.
Étape 9 : Alternative avec Balise Script CDN
Si vous préférez ne pas ajouter une dépendance npm, chargez React Scan via le composant Script de Next.js :
import Script from "next/script";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="fr">
<body>
{process.env.NODE_ENV === "development" && (
<Script
src="https://unpkg.com/react-scan/dist/auto.global.js"
strategy="beforeInteractive"
/>
)}
{children}
</body>
</html>
);
}Le build auto.global.js s'active automatiquement sans configuration. Idéal pour des investigations rapides, mais le paquet npm vous donne un contrôle complet via useScan et onRender.
Dépannage
La barre d'outils n'apparaît pas
Vérifiez que votre ReactScanProvider est bien un composant "use client" et qu'il est rendu avant le contenu de la page à l'intérieur de <body>.
Tous les composants clignotent en orange à chaque frappe
C'est attendu pour les champs de saisie contrôlés. Concentrez-vous d'abord sur les surbrillances grises — ce sont les re-rendus inutiles actionnables.
React Scan ralentit le serveur de développement
Définissez animationSpeed: "off" pour désactiver les animations visuelles tout en gardant le callback onRender actif. Cela réduit considérablement la surcharge visuelle.
useScan est indéfini au moment de l'exécution
Confirmez que vous importez depuis "react-scan" (le paquet npm) et non depuis le script CDN. L'API des hooks est uniquement exportée depuis le paquet npm.
Prochaines Étapes
- Combinez React Scan avec le React DevTools Profiler pour une analyse en profondeur des rendus coûteux.
- Branchez le callback
onRenderdans une assertion CI pour faire échouer un build quand le compte de rendus d'un composant critique dépasse un seuil. - Lisez le guide complémentaire sur React Compiler et la mémoïsation automatique pour voir comment le compilateur élimine les rendus que React Scan met en évidence.
- Utilisez React Scan avec TanStack Query pour repérer les abonnements à l'état serveur qui déclenchent plus de re-rendus que prévu.
Conclusion
React Scan vous offre une couche visuelle toujours active qui transforme les re-rendus React d'événements invisibles en surbrillances lumineuses et comptables. En suivant ce tutoriel, vous pouvez maintenant l'installer dans n'importe quel projet Next.js App Router, interpréter correctement les signaux orange et gris, corriger les re-rendus inutiles manuellement avec useMemo ou automatiquement avec React Compiler, et mettre en place un moniteur onRender programmatique pour détecter les régressions avant qu'elles n'atteignent la production.