Changer le thème

React Compiler + shadcn/ui : le développement frontend à l'ère de l'optimisation automatique

Easton editorial illustration: cost-quality-speed triangle

Ouvrez React DevTools : un composant shadcn Data Table se re-rend à chaque scroll — même quand les props n’ont pas changé. J’avais déjà plus de 20 useMemo écrits à la main, mais certaines situations limites m’échappaient encore. À ce moment-là, je me suis dit : si seulement un outil pouvait gérer ces optimisations automatiquement.

Deux mois plus tard, React Compiler v1.0 est sorti officiellement. Plus besoin de se demander s’il faut ajouter un useMemo à telle fonction, ni de craindre d’oublier un useCallback. Le compilateur s’en charge au moment du build.

Mais qu’est-ce que cela change pour un projet shadcn/ui ? Faut-il garder la mémorisation écrite à la main après activation du Compiler ? Les composants shadcn posent-ils des problèmes de compatibilité ? Cet article partage le retour d’expérience de ces dernières semaines.


TL;DR


1. Qu’est-ce que React Compiler ?

Franchement, React Compiler n’est pas une « révolution » — c’est plutôt un outil d’automatisation qui fait le travail d’optimisation que vous auriez dû écrire à la main.

Avant, à chaque problème de performance en React, il fallait ajouter manuellement useMemo, useCallback, React.memo. En oublier un seul, et toute la page pouvait ramer. Et cette logique de mémorisation pouvait devenir complexe : analyser les dépendances, gérer les cas limites, éviter la sur-optimisation.

L’idée de React Compiler : puisque ces optimisations suivent des règles, laissez le compilateur les analyser au build et insérer la mémorisation appropriée.

Par exemple, pour un Data Table, j’étais obligé d’écrire :

// Version optimisée à la main (fastidieuse)
const columns = useMemo(() => [
  {
    accessorKey: 'name',
    header: 'Name',
    cell: ({ row }) => row.original.name,
  },
  // ... autres définitions de colonnes
], []); // le tableau de dépendances à maintenir soi-même

const handleRowClick = useCallback((row) => {
  console.log('Clicked:', row);
}, []);

Après activation du Compiler, ce code peut disparaître :

// Version optimisée automatiquement par le Compiler (concise)
const columns = [
  {
    accessorKey: 'name',
    header: 'Name',
    cell: ({ row }) => row.original.name,
  },
];

const handleRowClick = (row) => {
  console.log('Clicked:', row);
};

Le compilateur analyse les dépendances de ces fonctions au build et décide automatiquement s’il faut les mémoriser. Fini le dilemme « faut-il ajouter un useMemo ? ».

La formulation officielle : « build-time performance optimization ». En clair, le compilateur fait le travail de React.memo à votre place.


2. Activer React Compiler : trois méthodes

Si vous utilisez Next.js 16, bonne nouvelle : le Compiler est déjà intégré. Les autres outils de build demandent une configuration supplémentaire.

Méthode 1 : Next.js 16 (le plus simple)

Next.js 16 intègre React Compiler par défaut. Il suffit d’ajouter une ligne dans next.config.js :

// next.config.js
const nextConfig = {
  experimental: {
    reactCompiler: true, // activer le Compiler
  },
};

export default nextConfig;

Tous les composants React du projet seront optimisés automatiquement. Aucun changement de code requis.

Méthode 2 : Vite + React Compiler Plugin

Pour un projet Vite, installez un plugin Babel :

npm install --save-dev babel-plugin-react-compiler

Puis configurez vite.config.ts :

// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [
    react({
      babel: {
        plugins: [
          ['babel-plugin-react-compiler', {
            // optionnel : spécifier le mode de compilation
            // 'mode': 'optimize'
          }],
        ],
      },
    }),
  ],
});

Vite appliquera alors le Compiler automatiquement au build.

Méthode 3 : configuration Babel autonome (autres outils)

Si vous utilisez webpack, Rollup ou un autre outil de build, ajoutez le plugin directement dans la config Babel :

// .babelrc ou babel.config.json
{
  "plugins": [
    ["babel-plugin-react-compiler"]
  ]
}

Tout outil de build utilisant Babel peut ainsi supporter le Compiler.


3. shadcn/ui + React Compiler : retour d’expérience

Voici les résultats concrets. J’ai migré un back-office shadcn/ui d’environ 40 composants. L’expérience globale est bonne, avec quelques détails à surveiller.

Scénario 1 : re-rendus du composant Dialog

Le Dialog shadcn est un composant pur typique — props inchangées, rendu inchangé. En optimisation manuelle, j’oubliais souvent d’ajouter useCallback sur onOpenChange.

Avec le Compiler, ce genre de problème disparaît. Il reconnaît onOpenChange comme une fonction stable (sans dépendance externe) et la mémorise automatiquement.

En test, à l’ouverture du Dialog, le composant parent ne se re-rend plus. Avant, chaque ouverture provoquait un re-rendu du parent — parce que onOpenChange était une nouvelle fonction à chaque fois.

Scénario 2 : Form + validation Zod

Le Form shadcn s’appuie sur React Hook Form + Zod. Avant, pour les règles de validation, j’écrivais :

// Version optimisée à la main
const formSchema = useMemo(() => z.object({
  username: z.string().min(2, 'Au moins 2 caractères'),
  email: z.string().email('Format d\'e-mail incorrect'),
}), []);

const onSubmit = useCallback((values) => {
  console.log(values);
}, []);

Maintenant, ces useMemo/useCallback sont supprimés :

// Version optimisée par le Compiler
const formSchema = z.object({
  username: z.string().min(2, 'Au moins 2 caractères'),
  email: z.string().email('Format d\'e-mail incorrect'),
});

const onSubmit = (values) => {
  console.log(values);
};

Le Compiler juge si ces fonctions sont stables. Si le schéma Zod renvoie le même objet à chaque fois (sans variable externe), il le mémorise automatiquement.

Scénario 3 : rendu du Data Table

Le Data Table shadcn repose sur TanStack Table. C’est le scénario le plus propice aux problèmes de performance — définitions de colonnes, tri, filtrage : chaque point pouvait exiger une optimisation manuelle.

Avec le Compiler, les fonctions de colonnes et les gestionnaires d’événements sont optimisés automatiquement. Mesure du nombre de rendus :

15/s
Nombre de rendus (normal)
Source: Tests Compiler vs optimisation manuelle
  • Version optimisée à la main : 15 rendus/s du tableau au scroll (normal)
  • Version non optimisée : 45 rendus/s au scroll (performances médiocres)
  • Version optimisée par le Compiler : même résultat que l’optimisation manuelle, 15 rendus/s

Résultat équivalent. Et 30 lignes de code d’optimisation manuelle en moins — la lisibilité s’améliore nettement.

Impact sur la taille du bundle

Beaucoup craignent que le Compiler grossisse le bundle. En pratique, l’impact est minime — la logique d’optimisation est insérée à la compilation, sans code runtime supplémentaire.

Comparaison :

  • Sans Compiler : bundle 142 Ko
  • Avec Compiler : bundle 144 Ko

+2 Ko, principalement la logique de mémorisation insérée. En échange, vous supprimez les useMemo/useCallback manuels (déjà présents dans le bundle) — l’impact global reste faible.


4. Migration : pièges à éviter

Le Compiler n’est pas parfait. Voici les écueils les plus fréquents lors de la migration.

1. Les conventions de nommage comptent

Le Compiler s’appuie sur le nommage pour inférer les dépendances. Si votre code ressemble à ceci :

// ❌ le Compiler peut mal analyser
function MyComponent(props) {
  return <div>{props.data.name}</div>;
}

Il peut ne pas identifier correctement la dépendance props.data. Préférez :

// ✅ nommage explicite
function MyComponent({ data }) {
  return <div>{data.name}</div>;
}

Le Compiler peut alors juger plus précisément la dépendance data.

Cette convention est aussi mentionnée dans la doc officielle React — déstructurer les props rend le code plus clair et facilite l’analyse du Compiler.

2. Les règles ESLint changent

Si votre projet utilise react-hooks/exhaustive-deps, les rapports de cette règle évoluent après activation du Compiler.

Avant, une dépendance useMemo manquante déclenchait une erreur ESLint. Maintenant, le Compiler gère les dépendances — la règle perd de son importance.

Ajustez la config ESLint : passez exhaustive-deps en « warn » ou désactivez-la. Le Compiler s’en charge ; la règle devient du bruit.

// .eslintrc
{
  "rules": {
    "react-hooks/exhaustive-deps": "off" // le Compiler gère les dépendances
  }
}

3. Compatibilité des bibliothèques tierces

Certaines bibliothèques tierces peuvent être incompatibles, surtout celles avec une logique d’effets de bord complexe en interne.

Si le build échoue ou si des erreurs apparaissent au runtime après activation :

  1. Lisez le message d’erreur — souvent un problème de nommage ou de logique dans un composant
  2. Désactivez le Compiler pour ce composant (commentaire 'use no memo')
  3. Investiguez progressivement pour identifier les composants incompatibles

J’ai rencontré une bibliothèque de drag-and-drop incompatible. Solution : désactiver le Compiler sur ce composant :

'use no memo'; // indiquer au Compiler : ne pas optimiser ce composant

function DraggableList() {
  // logique de drag-and-drop...
}

Le Compiler ignore alors ce composant et n’applique aucune optimisation automatique.

4. Quand désactiver le Compiler ?

La plupart des composants fonctionnent bien. Quelques cas où la désactivation est recommandée :

  • Effets de bord complexes : timers, animations, manipulation DOM directe — le Compiler peut mal analyser
  • Bibliothèque tierce incompatible : logique interne complexe, risque de mauvaise interprétation
  • Performances qui se dégradent : dans de rares cas extrêmes, une mémorisation excessive peut augmenter la consommation mémoire

Méthode : ajoutez le commentaire 'use no memo' pour que le Compiler ignore le composant.


5. De l’optimisation manuelle à l’automatique : journal de migration

Voici le processus concret. Projet : back-office avec plus de 40 composants shadcn/ui.

Code avant migration

Environ 50 useMemo/useCallback écrits à la main. La partie Data Table était la plus complexe — colonnes, tri, filtrage, tout optimisé manuellement.

Le code était lourd :

// Data Table avant migration (optimisation manuelle)
const columns = useMemo(() => [
  { accessorKey: 'id', header: 'ID' },
  { accessorKey: 'name', header: 'Name' },
  // ... autres colonnes
], []);

const sorting = useMemo(() => [{ id: 'name', desc: true }], []);

const handleSortingChange = useCallback((updater) => {
  setSorting(updater);
}, []);

Code après migration

Après activation du Compiler, toute l’optimisation manuelle a été supprimée :

// Data Table après migration (optimisation automatique)
const columns = [
  { accessorKey: 'id', header: 'ID' },
  { accessorKey: 'name', header: 'Name' },
];

const sorting = [{ id: 'name', desc: true }];

const handleSortingChange = (updater) => {
  setSorting(updater);
};

Code plus concis, bien plus lisible. Avant, il fallait vérifier chaque tableau de dépendances useMemo ; ce n’est plus nécessaire.

Comparaison de performances

Scores Lighthouse :

  • Avant migration : Performance 82, LCP 1,8 s
  • Après migration : Performance 85, LCP 1,6 s

Légère amélioration. Surtout grâce à la suppression d’optimisations manuelles superflues — le Compiler n’insère la mémorisation que là où c’est vraiment utile.

Temps de rendu mesuré :

  • Version optimisée à la main : ~12 ms en moyenne au scroll du Data Table
  • Version Compiler : ~10 ms en moyenne

Équivalent, voire légèrement meilleur. La logique d’optimisation du Compiler est solide.

Retours de l’équipe

Après la migration, j’ai recueilli l’avis de quelques collègues :

  • « Plus besoin de se demander s’il faut mémoriser, c’est plus simple »
  • « Sans tous ces useMemo, le code est beaucoup plus clair »
  • « Un composant incompatible, résolu avec ‘use no memo’, ça va »

Retour globalement positif. Moins de charge mentale — fini de se demander à chaque fonction s’il faut la mémoriser.


Résumé

React Compiler est une évolution importante de l’écosystème React. Pour un projet shadcn/ui, les avantages sont clairs :

  1. Optimisation automatique : plus de useMemo/useCallback manuels, le Compiler s’en charge au build
  2. Code simplifié : suppression de l’optimisation manuelle, meilleure lisibilité
  3. Moins de charge mentale : fini le dilemme « faut-il mémoriser ? »

Points d’attention lors de la migration :

  • Conventions de nommage : déstructurer les props, éviter props.data
  • Configuration ESLint : ajuster la règle exhaustive-deps
  • Compatibilité tierce : en cas de problème, utiliser 'use no memo'

Priorisez Next.js 16 pour tester — activation immédiate, configuration minimale. Pour les autres outils de build, suivez l’exemple Vite et migrez progressivement.

Franchement, après le Compiler, écrire du React ne ressemble plus tout à fait à avant — on écrit du JavaScript « normal », sans penser en permanence aux détails de performance. C’est agréable.


FAQ

FAQ

React Compiler augmente-t-il la taille du bundle ?
Pas de manière significative. En pratique, après activation du Compiler, le bundle n'augmente que d'environ 2 Ko, principalement à cause de la logique de mémorisation insérée par le compilateur. En contrepartie, vous supprimez les useMemo/useCallback écrits à la main — l'impact global reste minime.
Les composants shadcn/ui sont-ils compatibles avec React Compiler ?
La plupart des composants shadcn/ui sont des composants purs : le Compiler identifie bien les dépendances et optimise correctement. Certains composants avec une logique interne complexe peuvent nécessiter le commentaire 'use no memo' pour désactiver le Compiler. Migrez progressivement et désactivez au cas par cas.
Faut-il supprimer les useMemo/useCallback écrits à la main après activation du Compiler ?
Oui, c'est recommandé. Le Compiler insère automatiquement la mémorisation là où c'est nécessaire ; les useMemo/useCallback manuels deviennent souvent redondants. Après migration, le code est plus concis et les performances restent équivalentes, voire meilleures.
Dans quels cas faut-il désactiver le Compiler ?
Logique d'effets de bord complexe (timers, animations, manipulation DOM), incompatibilité avec une bibliothèque tierce, ou cas extrêmes où les performances se dégradent. Méthode : ajoutez le commentaire 'use no memo' en tête de composant.
Quelles exigences de nommage impose le Compiler ?
Privilégiez la déstructuration des props plutôt que props.data directement. Par exemple const { data } = props ou function MyComponent({ data }) — le Compiler analyse ainsi les dépendances plus précisément.
Comment activer le Compiler dans Next.js 16 et les projets Vite ?
Next.js 16 : ajoutez experimental: { reactCompiler: true } dans next.config.js. Vite : installez babel-plugin-react-compiler et configurez babel.plugins dans le plugin react de vite.config.ts.

9 min de lecture · Publié le: 31 mars 2026 · Mis à jour le: 27 juil. 2026

Commentaires

Connectez-vous avec GitHub pour laisser un commentaire

Easton BlogEaston Blog