ITSkillsCenter
Blog

React performance : optimisation pratique 2026

10 min de lecture

Lecture : 13 minutes · Niveau : avancé · Mise à jour : avril 2026

Une application React lente n’est pas une fatalité. Mais la performance s’améliore par méthode, pas par superstition : memo partout n’aide pas, optimiser sans mesurer aggrave souvent les choses. Ce guide rassemble la démarche pratique et les outils concrets pour rendre une app React fluide en 2026, dans un contexte où React Compiler change considérablement la donne.

Voir aussi → React pour PME : guide frontend pro.


Sommaire

  1. Mesurer avant d’optimiser
  2. React Compiler : le game changer 2026
  3. Comprendre les rerenders
  4. memo, useMemo, useCallback : quand vraiment
  5. Code splitting et lazy loading
  6. Virtualisation des listes
  7. Web Vitals et expérience perçue
  8. Bundle size et tree shaking
  9. Server Components et streaming
  10. FAQ

1. Mesurer avant d’optimiser

La règle d’or : ne jamais optimiser sans avoir mesuré. Beaucoup d’optimisations « intuitives » (envelopper tout dans memo, useMemo systématique) ajoutent du surcoût sans gain mesurable, voire dégradent la performance.

Les outils de mesure

  • React DevTools Profiler : enregistre les rendus, montre quels composants ont rendu et combien de temps
  • Performance tab du navigateur : flame chart détaillé, identifie les long tasks
  • Lighthouse : audit complet (perf, accessibilité, SEO) avec recommandations
  • Web Vitals extension Chrome : affiche LCP, INP, CLS en temps réel pendant la navigation
  • Sentry, LogRocket, Datadog RUM : performance en production réelle, pas en laboratoire

Identifier le bottleneck

Avant de toucher au code, répondre à : qu’est-ce qui est lent exactement ? Le chargement initial ? Les interactions ? Le scroll ? Les transitions de page ? Chaque problème a ses outils et ses solutions différents.

// Mesurer un calcul spécifique
console.time("filterItems");
const filtered = items.filter(complexPredicate);
console.timeEnd("filterItems");

Pour des mesures plus fines : performance.mark() et performance.measure().


2. React Compiler : le game changer 2026

Le React Compiler (sorti en stable en 2025) analyse le code à la compilation et applique automatiquement les mémorisations qui étaient manuelles. Concrètement :

// Avant React Compiler : optimisations manuelles
const filtered = useMemo(() => items.filter(p), [items, p]);
const handleClick = useCallback(() => doSomething(id), [id]);
const Card = React.memo(({ item }) => ...);

// Avec React Compiler : on écrit naturellement
const filtered = items.filter(p);
const handleClick = () => doSomething(id);
function Card({ item }) { ... }

Le compiler décide ce qui mérite d’être mémorisé en analysant les dépendances. Résultat : moins de boilerplate, plus de simplicité, optimisations souvent meilleures que ce qu’on faisait manuellement.

Activation

npm install -D babel-plugin-react-compiler

Configuration Vite ou Next.js pour activer le plugin. Documentation officielle React détaille les étapes.

Ce qui reste à faire manuellement

Le compiler n’optimise pas tout :

  • Code splitting et lazy loading
  • Virtualisation
  • Décisions d’architecture (sortir des composants, déplacer l’état)
  • Optimisations de network (caching, prefetching)

3. Comprendre les rerenders

Un composant React rerend quand : son state local change, ses props changent, son parent rerend, son context change.

Diagnostic

React DevTools Profiler enregistre les rerenders. Activer l’option « Highlight updates when components render » montre visuellement les composants qui rerend en surbrillance — utile pour voir ce qui se passe.

Causes fréquentes de rerenders excessifs

Création d’objets/fonctions à chaque rendu :

// Mauvais : nouveau objet à chaque rendu, MyChild rerend
function Parent() {
  return <MyChild config={{ timeout: 5000 }} />;
}

// Bon
const config = { timeout: 5000 };
function Parent() {
  return <MyChild config={config} />;
}

Context qui change trop souvent :

// Mauvais : tous les consommateurs rerend à chaque tick
<TimeContext.Provider value={Date.now()}>

State trop haut dans l’arbre :

// Mauvais : state au top, tout rerend
function App() {
  const [hover, setHover] = useState(false);
  return <DeepTree />;
}

// Bon : state au plus bas niveau possible
function ParticularComponent() {
  const [hover, setHover] = useState(false);
}

Mesurer le coût d’un rerender

Tous les rerenders ne sont pas problématiques. Un composant simple qui rerend 100 fois par seconde peut être indolore. Un composant complexe avec calculs ou effets peut être catastrophique même à 10 rerenders/seconde. Mesurer avant de conclure.


4. memo, useMemo, useCallback : quand vraiment

Avec React Compiler, la majorité de ces optimisations sont automatiques. Avant React 19 ou sans le compiler, voici les règles.

React.memo

Empêche un composant de rerender si ses props n’ont pas changé (comparaison shallow).

const Card = React.memo(function Card({ item }: { item: Item }) {
  return <div>{item.name}</div>;
});

Utiliser quand : composant qui rerend souvent à cause du parent, mais dont les props changent rarement, ET dont le rendu est coûteux.

Ne pas utiliser quand : props sont des objets/fonctions créés à chaque rendu (memo est inutile car les props changent toujours).

useMemo

Mémorise une valeur calculée tant que les dépendances ne changent pas.

const expensive = useMemo(() => computeExpensive(data), [data]);

Utiliser quand : calcul vraiment coûteux (>1ms typiquement), valeur stable utilisée comme dépendance d’autres hooks.

Ne pas utiliser quand : calcul trivial (filter, map sur petite liste). Le surcoût de useMemo (création de la fonction, vérification des deps) peut dépasser le gain.

useCallback

Identique à useMemo(() => fn, deps) mais pour fonctions.

const handleClick = useCallback(() => doStuff(id), [id]);

Utiliser quand : la fonction est passée à un composant memoizé ou utilisée comme dépendance d’un useEffect/useMemo.

Ne pas utiliser quand : la fonction n’est pas passée à un memo et n’est pas dans des deps. Pure cargaison gratuite.

Anti-pattern : memo partout

Envelopper tous les composants dans memo « par sécurité » est anti-productif : le coût de la comparaison shallow s’accumule, et beaucoup de memo n’évitent rien (props qui changent à chaque fois).


5. Code splitting et lazy loading

Charger 2 Mo de JavaScript pour afficher la première page est insensé. Le code splitting découpe le bundle en chunks chargés à la demande.

React.lazy

import { lazy, Suspense } from "react";

const Editor = lazy(() => import("./Editor"));

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      {showEditor && <Editor />}
    </Suspense>
  );
}

Editor n’est chargé que lorsqu’il est rendu pour la première fois.

Au niveau des routes

Avec React Router :

const router = createBrowserRouter([
  {
    path: "/",
    Component: lazy(() => import("./Home")),
  },
  {
    path: "/dashboard",
    Component: lazy(() => import("./Dashboard")),
  },
]);

Avec Next.js : automatique par page.

Préchargement intelligent

// Précharger un module quand l'utilisateur survole un lien
<a
  href="/dashboard"
  onMouseEnter={() => import("./Dashboard")}
>
  Dashboard
</a>

Le module est en cache quand l’utilisateur clique vraiment.


6. Virtualisation des listes

Rendre 10 000 items dans le DOM est lent : créer les nœuds, les attacher, les peindre. La virtualisation ne rend que les items visibles + un buffer.

TanStack Virtual

import { useVirtualizer } from "@tanstack/react-virtual";

function VirtualList({ items }: { items: Item[] }) {
  const parentRef = useRef<HTMLDivElement>(null);
  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 50,
  });

  return (
    <div ref={parentRef} style={{ height: "400px", overflow: "auto" }}>
      <div style={{ height: virtualizer.getTotalSize() }}>
        {virtualizer.getVirtualItems().map(v => (
          <div
            key={v.key}
            style={{
              position: "absolute",
              top: v.start,
              height: v.size,
              width: "100%",
            }}
          >
            {items[v.index].name}
          </div>
        ))}
      </div>
    </div>
  );
}

À utiliser dès qu’une liste dépasse ~100-200 items affichés simultanément. Indispensable pour des dashboards avec longues tables.


7. Web Vitals et expérience perçue

Google mesure trois indicateurs principaux :

  • LCP (Largest Contentful Paint) : temps avant le plus gros élément visible. Cible : < 2.5s
  • INP (Interaction to Next Paint) : temps de réponse aux interactions. Cible : < 200ms
  • CLS (Cumulative Layout Shift) : stabilité visuelle pendant le chargement. Cible : < 0.1

Améliorer LCP

  • Image principale prioritaire (fetchpriority="high")
  • Réduire le JavaScript initial (code splitting)
  • Server-side rendering (Next.js, Remix) plutôt que SPA pure
  • CDN pour les assets statiques
  • Préconnexion aux domaines critiques (<link rel="preconnect">)

Améliorer INP

  • Décomposer les long tasks (>50ms) avec setTimeout(0) ou scheduler.yield()
  • React 19 et concurrent rendering aident automatiquement
  • Web Workers pour les calculs CPU-intensifs

Améliorer CLS

  • Spécifier dimensions des images et iframes
  • Réserver l’espace pour le contenu chargé en différé
  • Éviter d’insérer du contenu au-dessus de ce que l’utilisateur regarde

8. Bundle size et tree shaking

Analyser le bundle

vite-bundle-visualizer ou @next/bundle-analyzer montrent visuellement ce qui pèse :

npx vite-bundle-visualizer

Identifier : grosses libs importées pour 2 fonctions, locales/icônes en entier, dépendances en double.

Tree shaking

Importer seulement ce qu’on utilise :

// Mauvais : import tout lodash
import _ from "lodash";

// Bon : import ciblé
import debounce from "lodash/debounce";

// Encore mieux : alternative plus petite
import { debounce } from "es-toolkit";

Date libraries

moment.js est lourd (~70kb). Préférer date-fns (tree-shakable, plus léger) ou dayjs (très petit, API moment-like).

Icônes

Lucide, Heroicons, Tabler : importer chaque icône individuellement plutôt que toute la lib.

// Bon
import { ChevronRight } from "lucide-react";

9. Server Components et streaming

Les React Server Components (Next.js 13+) permettent de rendre une partie du composant sur le serveur, avec zéro JavaScript envoyé pour cette partie.

// Composant serveur (par défaut dans app/)
async function ProductPage({ id }: { id: string }) {
  const product = await db.product.findUnique({ where: { id } });
  return (
    <article>
      <h1>{product.name}</h1>
      <Description text={product.description} />
      <AddToCartButton productId={id} />  {/* client */}
    </article>
  );
}

Description rend côté serveur (pas de JS). AddToCartButton est un Client Component (interactivité). Bénéfice énorme pour le bundle initial et le LCP.

Streaming

Next.js peut envoyer le HTML par morceaux : afficher le squelette tout de suite, puis remplir au fur et à mesure que les données serveur arrivent.

<Suspense fallback={<Skeleton />}>
  <SlowComponent />
</Suspense>

Le Suspense permet le streaming : le reste de la page s’affiche, ce composant arrive plus tard.

Voir aussi → React state management 2026 pour les implications côté état.


10. FAQ

Mon app rame, par où commencer ?

  1. Profiler React DevTools sur l’action lente
  2. Identifier le composant qui rend longtemps ou trop souvent
  3. Mesurer ce qui change : props ? state ? context ?
  4. Appliquer la correction la plus simple (sortir l’état, mémoriser, etc.)
  5. Revérifier que ça a amélioré

Optimisation guidée par mesures, pas par intuition.

Faut-il toujours faire du Server Side Rendering ?

Non. Pour un dashboard interne authentifié sans SEO : SPA Vite suffit. Pour un site public où Google compte : SSR (Next.js, Remix) apporte un gain réel sur LCP et SEO. Choisir selon le besoin.

React Server Components ou SSR classique ?

RSC est une étape supplémentaire qui réduit le JavaScript côté client (les composants serveur ne sont jamais envoyés en JS). SSR classique envoie tout le JavaScript pour l’hydratation. RSC est plus efficace pour les composants non-interactifs. Disponible dans Next.js 13+ App Router.

Mon bundle fait 1 Mo, c’est grave ?

Pour un dashboard authentifié : acceptable. Pour un site public mobile : trop. La cible saine est <300kb gzippé pour le bundle initial. Code splitting et tree shaking sont les leviers principaux.

Comment optimiser une longue table de données ?

Virtualisation avec TanStack Virtual ou react-window. Si la donnée vient du serveur, paginer côté serveur plutôt que tout charger. Memo sur les rows si elles sont coûteuses à rendre.

Animations qui rament : que faire ?

Préférer les animations CSS aux animations JS quand possible. Animer transform et opacity plutôt que width, height, top (qui forcent un layout). Pour des animations complexes, Framer Motion ou GSAP optimisent automatiquement.

Concurrent rendering, useTransition : utile en pratique ?

Oui pour les UI avec mises à jour fréquentes. useTransition marque une mise à jour comme « non-urgente » : React peut l’interrompre si une interaction utilisateur arrive entre-temps. Pratique pour les filtres en temps réel sur grosses listes.


Articles liés (cluster React)


Article mis à jour le 25 avril 2026. Pour signaler une erreur ou suggérer une amélioration, écrivez-nous.

Besoin d'un site web ?

Confiez-nous la Création de Votre Site Web

Site vitrine, e-commerce ou application web — nous transformons votre vision en réalité digitale. Accompagnement personnalisé de A à Z.

À partir de 250.000 FCFA
Parlons de Votre Projet
Publicité