Introduction
L'optimisation des performances Expo peut sembler une boîte noire lorsque votre application saccade pendant le défilement, perd des images lors de la navigation ou met trop de temps à démarrer sur un appareil Android bas de gamme. Vous soupçonnez que le problème vient de votre code, d'une dépendance ou d'un défaut de configuration Expo, mais le chemin du symptôme à la correction vérifiée est rarement documenté en un seul endroit.
Cet article est un guide pratique destiné aux développeurs, consultants DevOps et équipes techniques de startups qui utilisent des applications React Native propulsées par Expo. Il relie les thèmes communs de l'optimisation Expo, de la latence et des goulots d'étranglement à des commandes concrètes, des sorties attendues, des signaux d'échec et des décisions de récupération. Vous ne trouverez pas de conseils génériques comme « utilisez le mode production » sans exemple.
Chaque section suit le même schéma opérationnel : observer avant de modifier, limiter le rayon d'impact, utiliser des valeurs factices au lieu de secrets, vérifier le résultat et documenter comment récupérer si l'état attendu n'est pas atteint. C'est la différence entre deviner et opérer.
Après avoir lu cet article, vous serez capable de :
- Inventorier la version Expo et la topologie qui affectent les performances.
- Appliquer des modifications de configuration sûres avec un chemin de retour en arrière testé.
- Exécuter des vérifications et des diagnostics produisant des preuves numériques.
- Reconnaître les modes d'échec et récupérer sans reconstruction complète.
- Utiliser une liste de contrôle opérationnelle avant déploiement pour éviter les régressions courantes.
Inventaire des versions et de l'environnement
Avant d'optimiser une application Expo, identifiez les versions exactes et la topologie de déploiement. Les performances Expo sont fortement influencées par la version du SDK Expo, la version de React Native, l'utilisation d'Expo Go ou d'un build de développement, et l'exécution dans un simulateur, un appareil physique ou un build EAS de production.
Commencez par une observation en lecture seule. Exécutez les commandes suivantes à la racine de votre projet pour afficher l'environnement actuel sans rien modifier :
npx expo --version
npx expo config --type public
npx expo-env-info
Sortie attendue pour un projet sur SDK 50 en janvier 2025 :
5.0.0
{
"name": "my-app",
"slug": "my-app",
"version": "1.0.0",
"orientation": "portrait",
"userInterfaceStyle": "light",
"sdkVersion": "50.0.0",
"platforms": ["ios", "android", "web"]
}
expo-env-info 1.2.0 environment info:
System:
OS: macOS 14.3
Shell: 5.9 - /bin/zsh
Binaries:
Node: 20.11.0 - ~/.nvm/versions/node/v20.11.0/bin/node
Yarn: 1.22.19 - /usr/local/bin/yarn
npm: 10.2.4 - ~/.nvm/versions/node/v20.11.0/bin/npm
Watchman: 2024.01.22.00 - /usr/local/bin/watchman
Managers:
CocoaPods: 1.15.2 - /usr/local/bin/pod
SDKs:
iOS SDK:
Platforms: DriverKit 23.2, iOS 17.2, macOS 14.2, tvOS 17.2, watchOS 10.2
Android SDK:
API Levels: 33, 34
Build Tools: 33.0.2, 34.0.0
System Images: android-33 | Google APIs ARM 64
IDEs:
Android Studio: 2023.1 AI-231.9392.1.2311.11076708
Xcode: 15.2/15C500b - /usr/bin/xcodebuild
npmPackages:
expo: ~50.0.0 => 50.0.0
react: 18.2.0 => 18.2.0
react-native: 0.73.2 => 0.73.2
npmGlobalPackages:
eas-cli: 7.1.3
Expo Workflow: managed
Enregistrez cette sortie dans un fichier nommé perf-baseline-AAAA-MM-JJ.txt, où AAAA-MM-JJ est la date. Cette référence servira de base de comparaison ultérieure. Ne placez pas ce fichier sous contrôle de version s'il contient des chemins internes ou des identifiants d'appareil ; stockez-le dans un dossier local sécurisé.
Ensuite, identifiez la topologie de déploiement avec des vérifications en lecture seule :
# S'agit-il d'un projet en flux géré (managed) ou nu (bare) ?
npx expo config --type introspect | grep -E '"workflow"|"isDetached"'
# Utilisez-vous Expo Go ou un build de développement personnalisé ?
ls ios/android 2>/dev/null || echo "Pas de répertoires natifs ; probablement Managed avec Expo Go"
# Vérifiez les profils de build EAS s'ils existent
eas config
Exemple de sortie de eas config :
{
"cli": {
"version": ">= 7.1.0"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"preview": {
"distribution": "internal"
},
"production": {
"autoIncrement": true
}
},
"submit": {
"production": {}
}
}
Si vous voyez "workflow": "managed" et aucun répertoire ios/ ou android/, vous êtes en flux géré (Managed). Si vous avez des répertoires natifs, vous êtes en flux nu (Bare). L'optimisation des performances diffère : en Managed, de nombreux modules natifs sont précompilés et vous ne pouvez pas modifier certains paramètres ; en Bare, vous pouvez modifier le code natif mais devez être plus prudent lors des mises à niveau.
Une erreur de performance courante est de tester uniquement sur un simulateur haut de gamme alors que les utilisateurs ont des appareils Android bas de gamme. Pour révéler les goulots d'étranglement spécifiques à l'appareil, collectez une référence d'appareil :
# Sur un appareil Android physique connecté en USB avec le débogage USB activé
adb shell getprop ro.product.model
adb shell getprop ro.build.version.release
adb shell dumpsys meminfo <votre.nom.de.paquet>
Remplacez <votre.nom.de.paquet> par votre ID d'application réel, par exemple com.masociete.monapp. Si vous ne connaissez pas le nom du paquet sur Android, exécutez adb shell pm list packages | grep -i monapp pour le découvrir. Si le nom du paquet est un identifiant de production, masquez-le dans vos notes.
Sortie partielle attendue de dumpsys meminfo :
Applications Memory Usage (in Kilobytes):
Uptime: 12345678 Realtime: 12345678
** MEMINFO in pid 12345 [com.masociete.monapp] **
Pss Private Private SwapPss Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
Native Heap 12345 12345 0 0 20480 15000 5000
Dalvik Heap 6789 6789 0 0 16384 12000 4000
Stack 512 512 0 0
Ashmem 0 0 0 0
Other dev 123 123 0 0
.so mmap 4321 1000 3000 0
.apk mmap 123 123 0 0
.dex mmap 5678 500 4000 0
.oat mmap 100 100 0 0
.art mmap 2000 1000 500 0
Other mmap 300 300 0 0
Unknown 1000 1000 0 0
TOTAL 33345 23445 7500 0 36864 27000 9000
Enregistrez les nombres TOTAL Pss et Heap Alloc. Si le Pss total dépasse 150 Mo sur un appareil avec 2 Go de RAM, l'application est en pression mémoire et peut être tuée ou saccadée. Si vous n'avez pas d'appareil Android physique, utilisez un émulateur Android avec un profil de RAM faible (par exemple, créez un AVD avec 1024 Mo de RAM et API niveau 28 pour imiter un appareil de milieu de gamme).
Seulement après cette référence en lecture seule devez-vous envisager un changement. Chaque changement doit être limité, réversible et vérifié. Par exemple, si vous décidez de passer du SDK 49 au SDK 50, exécutez :
npx expo install expo@^50.0.0
npx expo install --fix
npx expo-doctor
Le rayon d'impact d'une mise à niveau SDK inclut toutes les dépendances et les projets natifs. Ayez un plan de retour : validez l'état actuel dans git, étiquetez-le comme pre-upgrade-sdk-49, et si la mise à niveau casse les performances, exécutez git revert ou git checkout pre-upgrade-sdk-49 suivi de yarn install et npx expo start --clear.
Chemin de configuration sûr
L'optimisation des performances Expo nécessite souvent des modifications de app.json, babel.config.js ou metro.config.js. Le chemin sûr consiste à modifier un paramètre à la fois, à observer l'effet et à conserver l'ancienne valeur dans un diff pour réversion.
Commencez par un paramètre de performance courant : le découpage du bundle JS et le mode production. Dans app.json, vous pouvez activer le moteur JavaScript Hermes et définir explicitement jsEngine. Hermes est le défaut depuis le SDK 47, mais le confirmer est utile.
Bloc recommandé actuel dans app.json pour le SDK 50 :
{
"expo": {
"name": "My App",
"slug": "my-app",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"assetBundlePatterns": ["**/*"],
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.masociete.monapp"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"package": "com.masociete.monapp"
},
"web": {
"favicon": "./assets/favicon.png"
},
"plugins": [
["expo-build-properties", {
"android": {
"compileSdkVersion": 34,
"targetSdkVersion": 34,
"buildToolsVersion": "34.0.0"
},
"ios": {
"deploymentTarget": "13.4"
}
}]
],
"jsEngine": "hermes"
}
}
Avant de faire cette modification, vérifiez votre app.json actuel avec un outil de diff. Si jsEngine est déjà "hermes", ne le changez pas. S'il est manquant, l'ajouter force Hermes et peut améliorer le temps de démarrage et l'utilisation de la mémoire. Le rayon d'impact est limité au moteur JS ; si vous voyez de nouveaux plantages, supprimez la clé et reconstruisez.
Une mesure concrète de l'impact de Hermes peut être obtenue en comparant les temps de démarrage avec et sans Hermes. Utilisez les étapes suivantes dans un build de développement :
- Construisez une fois avec Hermes activé (
"jsEngine": "hermes"). - Mesurez le temps jusqu'à l'interactivité en utilisant
npx expo start --dev-clientet un outil de profilage comme React Native Debugger ou Flipper (si disponible) ou un minuteur manuel. - Changez
"jsEngine": "jsc"temporairement et reconstruisez. - Mesurez à nouveau. Sur un appareil Android de milieu de gamme, Hermes réduit généralement le démarrage à froid de 3,2 secondes à 2,1 secondes et abaisse la mémoire de 15 Mo.
- Revenez au moteur le plus rapide.
Un autre chemin de configuration sûr consiste à optimiser le bundling Metro pour le développement. Dans metro.config.js, vous pouvez activer les require inline et la minification pour les builds de production. Cependant, Expo fournit des valeurs par défaut déjà ajustées. Un changement qui aide parfois est de réduire le nombre de modules surveillés par Metro. Dans un grand monorepo, ajoutez :
// metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname);
config.watchFolders = [
// Ajoutez uniquement les dossiers dont vous avez besoin
__dirname + '/src',
__dirname + '/assets'
];
config.resolver.blockList = [
/node_modules\/react-native\/.*__tests__\/.*/,
/node_modules\/react-native\/.*__mocks__\/.*/
];
module.exports = config;
Cela réduit la surcharge de surveillance des fichiers et peut améliorer la réactivité de Metro de 200 ms à 50 ms par reconstruction dans un monorepo avec 10 000 fichiers. Pour vérifier l'amélioration, exécutez npx expo start --clear et observez le temps de bundling Metro dans la sortie du terminal, qui affiche Bundled 1234 modules in 4321ms. Notez les nombres avant et après.
Ne placez jamais de véritables informations d'identification dans un changement de configuration. Par exemple, si vous ajoutez une variable d'environnement pour l'URL de l'API, utilisez une valeur factice comme https://api.example.com et documentez qu'elle doit être remplacée. Le chemin de récupération pour tout changement de configuration est de supprimer le bloc ajouté et de redémarrer le bundler.
Vérification et diagnostics
La vérification des performances Expo nécessite des preuves numériques. Les métriques les plus utiles sont :
- Temps de démarrage à froid : du tapotement sur l'icône de l'application à la première image interactive.
- Temps de démarrage à chaud : de l'arrière-plan au premier plan.
- Taux de rafraîchissement pendant une animation de défilement.
- Utilisation de la mémoire pendant le rendu d'une liste lourde.
- Taille du bundle JS et temps de chargement.
Pour mesurer le temps de démarrage à froid par programmation, utilisez expo-splash-screen et un marqueur de performance dans votre code d'application. En haut de votre composant principal, ajoutez :
import * as SplashScreen from 'expo-splash-screen';
import { useEffect } from 'react';
SplashScreen.preventAutoHideAsync();
export default function App() {
useEffect(() => {
const start = Date.now();
// Simuler le travail initial
fetchData().then(() => {
const end = Date.now();
console.log(`Temps jusqu'à l'interactivité : ${end - start} ms`);
SplashScreen.hideAsync();
});
}, []);
// ...
}
Cela enregistre le temps après votre premier chargement de données. En production, envoyez cette métrique à votre outil d'analyse. Si vous n'avez pas d'analyse, utilisez console.log et capturez les journaux via adb logcat ou npx expo start --dev-client et lisez la sortie du terminal.
Pour mesurer la taille du bundle JS, utilisez npx expo export et inspectez la sortie :
npx expo export --platform android --output-dir dist
ls -lh dist/_expo/static/js/android/*.hbc
Sortie attendue :
-rw-r--r-- 1 user staff 1.2M Jan 15 10:30 dist/_expo/static/js/android/App-abc123.hbc
Un bundle de plus de 3 Mo non compressé peut être un problème. Vous pouvez exécuter npx react-native bundle --platform android --dev false --entry-file index.js --bundle-output /tmp/bundle.js puis utiliser source-map-explorer pour visualiser le bundle :
npx source-map-explorer /tmp/bundle.js
Cela ouvre une visualisation. Cherchez les grandes dépendances. Par exemple, si vous importez lodash en entier, cela peut ajouter 70 Ko ; passez à lodash-es avec le tree shaking ou importez uniquement la fonction nécessaire.
Pour le diagnostic du taux de rafraîchissement, utilisez le React Native Performance Monitor. Dans votre application, ajoutez une superposition temporaire :
import { PerformanceMonitor } from 'react-native-performance-monitor';
// Dans votre composant
<PerformanceMonitor />
Exécutez un test de défilement sur un appareil bas de gamme et observez le compteur FPS. S'il descend en dessous de 55 FPS pendant un défilement FlatList, vous avez un goulot d'étranglement de rendu. Utilisez le Profiler de React DevTools pour trouver quel composant se re-rend excessivement. Dans React Native 0.73 avec la New Architecture activée, vous pouvez utiliser npx react-native profile ou l'onglet intégré Performance dans DevTools.
La vérification doit toujours comparer avant et après. Pour chaque changement, enregistrez un petit tableau :
| Métrique | Avant le changement | Après le changement | Amélioration |
|---|---|---|---|
| Temps de démarrage à froid (ms) | 3200 | 2100 | 34 % plus rapide |
| Taille du bundle JS (Mo) | 2.4 | 1.8 | 25 % plus petit |
| FPS moyen pendant le défilement | 48 | 57 | 9 FPS |
| Utilisation de la mémoire (Mo) | 180 | 150 | 30 Mo de moins |
Si l'amélioration attendue n'est pas observée, ne conservez pas le changement. Revenez en arrière en utilisant votre diff sauvegardé ou git et réévaluez.
Modes d'échec et récupération
L'optimisation des performances Expo peut introduire des échecs. Les modes d'échec courants sont :
- L'application plante au démarrage après l'ajout d'un module natif ou d'un changement de configuration.
- Rechargement infini ou erreurs de bundling après modification de
metro.config.jsoubabel.config.js. - Performances dégradées (mémoire plus élevée, FPS plus bas) après l'activation d'une fonctionnalité.
- Échecs de build sur EAS Build en raison de versions SDK incompatibles.
Chaque échec nécessite un chemin de récupération documenté.
Exemple 1 : Plantage au démarrage après le changement de jsEngine vers Hermes. Chemin de récupération :
- Arrêtez le bundler et tuez l'application sur l'appareil.
- Revenez sur
app.jsonpour supprimer"jsEngine": "hermes"ou le remettre à"jsc". - Videz les caches :
npx expo start --clear. - Reconstruisez le client de développement :
npx expo run:androidounpx expo run:ios. - Si le plantage persiste, vérifiez les journaux avec
adb logcat | grep -i erroret cherchez les erreurs d'initialisation Hermes. - Si le plantage persiste, exécutez
npx expo-doctorpour vérifier les conflits de dépendances.
Exemple 2 : Erreurs du bundler Metro après l'ajout de motifs blockList. Chemin de récupération :
- Supprimez les ajouts
blockListouwatchFoldersdemetro.config.js. - Exécutez
npx expo start --clear. - Si Metro échoue toujours, supprimez
node_moduleset réinstallez :rm -rf node_modules && yarn install. - Vérifiez en exécutant
npx expo export --platform android --output-dir /tmp/test-export; si cela réussit, la configuration Metro est valide.
Exemple 3 : Augmentation de l'utilisation de la mémoire après l'activation de la New Architecture. Chemin de récupération :
- Dans
app.json, définissez"newArchEnabled": falsesi vous l'aviez activé viaexpo-build-properties. - Reconstruisez l'application.
- Mesurez à nouveau la mémoire. Si elle revient à la référence, la New Architecture n'est peut-être pas prête pour vos dépendances.
- Gardez le changement hors service et signalez le problème aux mainteneurs de la bibliothèque.
Gardez toujours une étiquette git avant de faire des changements de performance, par exemple git tag perf-baseline-before-hermes. Ensuite, la récupération peut être git checkout perf-baseline-before-hermes -- app.json suivi d'une reconstruction. C'est plus rapide que de modifier manuellement les fichiers.
De plus, simulez un échec avant qu'il ne se produise en production. Pour les changements de configuration, exécutez d'abord l'application dans un environnement de staging. Utilisez un profil EAS Build de staging avec des identifiants de bundle différents et pas de vraies clés API. Ainsi, si l'application plante, cela n'affecte pas les utilisateurs en production.
Liste de contrôle opérationnelle
Utilisez cette liste de contrôle avant et après toute tâche d'optimisation des performances Expo. Elle est conçue pour un praticien qui doit éviter les étapes manquées et garantir un résultat vérifié.
| Étape | Action | Commande ou outil | Résultat attendu | Notes |
|---|---|---|---|---|
| 1 | Enregistrer l'environnement actuel | npx expo-env-info | Versions SDK, RN, Node listées | Sauvegarder la sortie dans un fichier de référence |
| 2 | Vérifier les clés de performance dans la configuration de l'application | Ouvrir app.json | jsEngine est "hermes" ou absent | Si absent, envisager de l'ajouter |
| 3 | Mesurer le temps de démarrage actuel | Minuteur manuel ou journal de code | Exemple : 2500 ms | Exécuter sur un appareil bas de gamme |
| 4 | Mesurer la taille actuelle du bundle | npx expo export puis ls -lh | Exemple : 2,1 Mo | Noter la compression |
| 5 | Mesurer le FPS de référence | React Native Performance Monitor | Exemple : 52 FPS | Pendant un test de défilement |
| 6 | Identifier un goulot d'étranglement de performance | Profiler React DevTools ou carte source | Plus grand composant ou morceau de bundle | Prioriser par impact utilisateur |
| 7 | Appliquer un changement limité | Modifier la configuration ou le code | Changement documenté dans le diff | Garder l'ancienne valeur pour réversion |
| 8 | Re-mesurer la métrique affectée | Identique aux étapes 3/4/5 | Comparer avant/après | Si pas d'amélioration, revenir en arrière |
| 9 | Exécuter les diagnostics Expo | npx expo-doctor | Aucune erreur ou avertissement | Corriger tout problème |
| 10 | Tester sur plusieurs appareils | Au moins un Android bas de gamme et un iOS | Pas de plantage, performance acceptable | Utiliser un laboratoire d'appareils si possible |
| 11 | Documenter le changement et le chemin de récupération | Écrire dans le wiki d'équipe ou le message de commit git | Étapes de retour claires | Inclure les nombres avant/après |
| 12 | Planifier la surveillance en production | Ajouter la journalisation du temps de démarrage ou l'analyse | Métrique collectée | Définir un seuil d'alerte |
Cette liste de contrôle peut être imprimée ou stockée comme fichier Markdown dans le dépôt du projet sous docs/perf-checklist.md.
Un exemple concret d'identification et de correction d'un goulot d'étranglement :
Scénario : Une FlatList de 500 éléments défile à 30 FPS sur un Samsung A12.
- Profilez avec React DevTools : constatez que le composant
ListItemse re-rend à chaque défilement en raison d'un nouveau callbackonPresscréé en ligne. - Correction : utilisez
useCallbackpour le gestionnaire ou passez àReact.memoavec des props appropriées. - Re-mesurez : le FPS passe à 55.
- Documentez : dans le message de commit, notez le FPS avant/après et le changement de code.
Avec cette liste de contrôle, vous réduisez le risque de régressions de performance et avez une piste d'audit claire.
Conclusion
L'optimisation des performances Expo est plus utile lorsque chaque recommandation est limitée à une version, observable et réversible. Copier une commande sans vérifier les prérequis et la sortie attendue n'est pas une procédure opérationnelle ; c'est une recette pour une application cassée.
Dans cet article, vous avez vu comment inventorier votre environnement, apporter des modifications de configuration sûres, vérifier avec des métriques concrètes et récupérer des échecs courants. Les exemples ont utilisé de vraies commandes et sorties attendues, avec des valeurs factices uniquement lorsque nécessaire, et ont toujours inclus un chemin de retour.
Comme prochaine étape, choisissez une vérification à faible risque dans la liste de contrôle opérationnelle. Par exemple, mesurez votre temps de démarrage à froid sur un appareil Android de milieu de gamme en utilisant l'extrait de code dans la section Vérification. Enregistrez l'état actuel dans un fichier de référence, exécutez la mesure et comparez-la avec l'attente de l'industrie de moins de 3 secondes pour une application simple. Si votre application est plus lente, identifiez le plus grand contributeur en utilisant source-map-explorer ou React DevTools, et appliquez une correction limitée.
N'oubliez pas de passer en revue les dépendances telles que React Native, les outils de build Android et votre pipeline CI/CD (par exemple, une configuration GitLab CI/CD qui peut utiliser une ancienne image Expo). Un flux de travail technique fiable rend les échecs visibles, protège les valeurs sensibles, limite les changements à la ressource prévue et définit la vérification de récupération avant qu'un incident ne force la décision.
Avec cette approche systématique, vous pouvez transformer l'optimisation des performances Expo de conjectures en une pratique mesurable et reproductible qui maintient votre application réactive et vos utilisateurs heureux.