Sur des appareils bas de gamme, une application React Native peut vite sembler lente, saccadée ou énergivore si on ne prend pas quelques précautions dès la conception. J’ai passé beaucoup de temps à optimiser des projets pour des marchés où les téléphones ont peu de RAM, des CPU modestes et des réseaux instables. Voici les approches et astuces concrètes que j’applique systématiquement — avec des exemples pratiques que vous pouvez intégrer immédiatement.
Commencez par mesurer — profiler avant d'optimiser
Avant toute modification, je profile l’application pour identifier les vrais goulots d’étranglement. Les optimisations aveugles coûtent du temps et peuvent introduire des bugs.
Outils : Android Profiler (CPU/Memory), Xcode Instruments, React Native Performance Monitor (dans le menu développeur), Flipper (React DevTools, Hermes plugin), systrace.Ce que je cherche : frames manquantes (jank), pics de CPU, fuite mémoire, blocages du JS thread (long tasks > 50ms).Une fois les hotspots identifiés (ex : rendu d’un écran, grosse opération JSON, animation), on peut cibler précisément les corrections.
Optimiser le bundle et le démarrage
Le temps de démarrage et la taille du bundle sont critiques sur des appareils limités.
Activez Hermes (Android + iOS si possible) : réduction du temps de démarrage et meilleure mémoire pour de nombreux cas. Hermes améliore aussi le GC et les performances du JS thread.Activez l’option "inlineRequires" dans le metro config pour charger les modules à la demande.Minifiez et enlevez les logs : supprimez console.log et debug statements en production (ou remplacez par un logger conditionnel).Réduisez les dépendances : chaque librairie peut ajouter du JS, du natif et du poids. Auditez node_modules (why-did-you-add est utile).Splittez le code : import dynamique (React.lazy / loadable) pour différer les composants non critiques.Réduire la charge du thread JS
Beaucoup de lenteurs viennent d’un JS thread saturé — opérations lourdes, JSON.parse massif, rendu excessif.
Déplacez les calculs lourds en natif ou sur un thread séparé (WebWorkers / react-native-threads / JSI native modules). Pour le parsing JSON volumineux, pensez à le faire côté natif ou en streaming.Évitez les boucles longues et les traitements synchrones pendant le rendu. Utilisez InteractionManager.runAfterInteractions pour différer le travail non critique.Batching d’événements : limitez la fréquence des mises à jour d’état (debounce, throttle) surtout pour les événements scroll/touch.Maîtriser les rendus — éviter les re-renders inutiles
Le re-rendering excessif est un classique. Je privilégie des composants purs et des stratégies prévisibles.
Utilisez React.memo pour les composants fonctionnels et PureComponent pour les classes quand c’est pertinent.Utilisez useCallback et useMemo pour stabiliser les références passées en props (éviter la recréation systématique de fonctions/objets à chaque render).Ne passez pas des objets créés inline en props ; utilisez des constantes ou memoization.Pour les listes longues, employez FlatList ou SectionList avec :getItemLayout si la hauteur est connue — améliore le scroll et évite le calcul layout à chaque frame.initialNumToRender et maxToRenderPerBatch ajustés pour ne pas rendre trop d’items d’un coup sur les appareils faibles.removeClippedSubviews : parfois utile pour réduire la mémoire utilisée par des views hors écran.Animations : privilégier le travail natif
Les animations sont souvent la première chose qui fait « ramer ». Utilisez le moteur natif quand c’est possible.
Utilisez Animated.useNativeDriver (opacity, transform) pour libérer le JS thread.Considérez Reanimated 2 (JSI) qui exécute beaucoup d’opérations côté moteur natif et permet des animations fluides même sur des devices modestes.Évitez les animations layout complexes (mesures et reflows fréquents). Privilégiez les transforms qui ne provoquent pas de recalculs de layout.Images et médias : gains rapides
Les images mal optimisées plombent mémoire et réseau. C’est souvent un levier simple et efficace.
Servez des images adaptées à la densité d’écran (2x/3x) et redimensionnées côté serveur quand possible.Utilisez des formats modernes : WebP souvent plus léger que JPG/PNG.Remplacez Image par react-native-fast-image pour un cache natif performant.Chargez les images en lazy loading et affichez des placeholders basiques (low-res / blurhash) pendant le chargement.Mémoire : repérer et corriger les fuites
Sur les appareils bas de gamme, la moindre fuite provoque un out-of-memory et kill de l’app. Je surveille cela régulièrement.
Utilisez le profilage mémoire d’Android Studio et Xcode pour détecter les objets qui s’accumulent.Désinscrivez les listeners, timers, subscriptions (WebSocket, DeviceEventEmitter) dans useEffect cleanup/ componentWillUnmount.Évitez de conserver de gros objets en mémoire (gros tableaux) ; préférez une pagination ou un stockage temporaire côté natif / disque.Optimisations natives et configuration
Certaines optimisations passent par le natif ou par la configuration de build :
Activez ProGuard / R8 sur Android et configurez la minification correctement pour réduire la taille APK/AAB.Sur Android, vérifiez la configuration du gradle (minSdk, ABI splits) pour réduire la taille et la surcharge.Préchauffage Hermes : générez des bytecode Hermes en build pour accélérer le cold start.Considérez les modules natifs pour les tâches intensives (par ex. traitement d’image, chiffrement, parsing).Bonnes pratiques d’architecture pour la performance
Un design d’architecture réfléchi empêche de devoir micro-optimiser partout.
Séparez clairement la logique métier du rendu UI. Les heavy computations doivent rester hors composants UI.Adoptez une gestion d’état locale pour les composants (useState/useReducer) et évitez de tout stocker globalement si non nécessaire (Redux/Context peut provoquer des re-renders larges).Pour le state global, utilisez des sélecteurs ou des subscriptions ciblées (reselect, Zustand, Jotai) pour ne mettre à jour que les composants qui en ont besoin.Checklist rapide (tableau)
| Aspect | Action |
| Bundle | Hermes, inlineRequires, code-splitting |
| JS Thread | Déplacer calculs lourds, InteractionManager, worker natifs |
| Rendus | React.memo, useCallback, FlatList optimisée |
| Animations | Native driver, Reanimated 2 |
| Images | WebP, fast-image, lazy loading |
| Mémoire | Profilage, cleanup des listeners, pagination |
Exemples pratiques
Quelques extraits que j’utilise souvent :
Stabiliser une callback :
<script>
const onPress = useCallback(() => { doSomething(item.id); }, [item.id]);
</script>
Optimiser FlatList :
<script>
<FlatList
data={items}
keyExtractor={i => i.id}
initialNumToRender={10}
maxToRenderPerBatch={5}
windowSize={5}
getItemLayout={(data, index) => ({length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index})}
>
</FlatList>
</script>
Ces détails réduisent drastiquement le travail du moteur de rendu sur les téléphones limités.
Workflow de test et déploiement
Enfin, testez sur de vrais appareils bas de gamme (ou émulateurs configurés) avant de publier. J’intègre toujours dans mes pipelines CI un build de test minifié et quelques benchmarks automatiques si possible.
Surveillez les crashs et ANR via Sentry / Bugsnag et mesurez le temps de cold start en conditions réelles.Collectez des metrics d’usage et performance (durée d’affichage du premier écran, jank rates) pour itérer en se basant sur des données réelles.Je pourrais détailler chaque point pendant des heures, mais l’idée essentielle est de mesurer, prioriser et appliquer des optimisations pragmatiques : réduire la charge côté JS, préférer le natif pour le lourd, optimiser images et listes, et utiliser les bons outils (Hermes, Reanimated, FlatList bien configurée). Les gains cumulés transforment l’expérience sur des appareils bas de gamme — et c’est souvent ce qui fait la différence entre une app frustrante et une app agréable à utiliser.