Votre application React rame ? Re-renders intempestifs, bundle trop lourd, interactions lentes ? Les problèmes de performance React sont courants mais solubles. Voici les techniques d'optimisation qui font vraiment la différence.
Comprendre les re-renders
React re-rend un composant quand :
- Son state change
- Ses props changent
- Son parent re-rend (même si les props n'ont pas changé)
Le problème : des re-renders inutiles qui cascadent dans tout l'arbre de composants.
React DevTools Profiler
Avant d'optimiser, mesurez. Le Profiler montre :
- Quels composants re-rendent
- Combien de temps prend chaque render
- Pourquoi un composant a re-rendu
Règle d'or : optimisez ce qui est mesuré comme problématique, pas ce qui vous semble suspect.
Mémoization des composants
React.memo
Empêche un composant de re-rendre si ses props n'ont pas changé :
const ExpensiveComponent = React.memo(({ data }) => {
// render coûteux
return <div>{/* ... */}</div>;
});
Attention : memo fait une comparaison shallow des props. Les objets et tableaux recréés à chaque render invalident le memo.
useMemo
Mémoize une valeur calculée :
const sortedItems = useMemo(() => {
return items.sort((a, b) => a.name.localeCompare(b.name));
}, [items]);
Utile pour : calculs coûteux, transformation de données, objets passés en props.
useCallback
Mémoize une fonction pour éviter de casser le memo d'un composant enfant :
const handleClick = useCallback(() => {
doSomething(id);
}, [id]);
Utile quand la fonction est passée en prop à un composant mémoizé.
Gestion du state
Colocate state
Le state doit vivre au plus près de là où il est utilisé. Un state haut dans l'arbre cause des re-renders de tout ce qui est en dessous.
Découper les composants
Isolez les parties qui changent souvent :
// Mauvais : tout re-rend quand le timer change
function Page() {
const [time, setTime] = useState(new Date());
return (
<div>
<Header />
<Timer time={time} />
<ExpensiveContent />
</div>
);
}
// Mieux : seul Timer re-rend
function Page() {
return (
<div>
<Header />
<TimerContainer />
<ExpensiveContent />
</div>
);
}
State management externe
Pour le state global, utilisez des solutions qui permettent des subscriptions granulaires :
- Zustand : simple et performant
- Jotai : atomique, très granulaire
- Redux Toolkit : avec selectors optimisés
Optimisation des listes
Keys stables
Utilisez des identifiants uniques et stables, jamais l'index :
// Mauvais
items.map((item, index) => <Item key={index} />)
// Bon
items.map((item) => <Item key={item.id} />)
Virtualisation
Pour les longues listes, ne rendez que les éléments visibles :
- react-window : léger et efficace
- react-virtuoso : plus de fonctionnalités
- TanStack Virtual : headless, très flexible
Lazy loading
React.lazy
Chargez les composants lourds à la demande :
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<Loading />}>
<HeavyComponent />
</Suspense>
);
}
Route-based splitting
Avec React Router, chaque route peut être un chunk séparé :
const Dashboard = React.lazy(() => import('./pages/Dashboard'));
const Settings = React.lazy(() => import('./pages/Settings'));
Optimisation du bundle
Analyser le bundle
Utilisez webpack-bundle-analyzer ou source-map-explorer pour visualiser ce qui prend de la place.
Tree shaking
Importez uniquement ce dont vous avez besoin :
// Mauvais : importe toute la lib import _ from 'lodash'; // Bon : importe seulement la fonction import debounce from 'lodash/debounce';
Dependencies légères
Alternatives légères aux libs courantes :
- date-fns au lieu de moment
- clsx au lieu de classnames
- zustand au lieu de redux (si adapté)
Images et assets
- Format WebP/AVIF
- Lazy loading natif (loading="lazy")
- srcset pour le responsive
- Placeholder blur pendant le chargement
Server Components (React 18+)
Avec Next.js App Router, les Server Components :
- Rendent côté serveur sans JS client
- Réduisent drastiquement le bundle
- Accèdent directement aux données (pas de useEffect)
Métriques à surveiller
- LCP : temps d'affichage du contenu principal
- INP : réactivité aux interactions
- Bundle size : taille du JavaScript chargé
- Time to Interactive : moment où l'app devient utilisable
Conclusion
La performance React n'est pas de la magie. C'est une combinaison de bonnes pratiques : éviter les re-renders inutiles, charger le code à la demande, et mesurer avant d'optimiser. Commencez par le Profiler, identifiez les vrais problèmes, puis appliquez les solutions appropriées.