Optimalizácia štartu React Native 2026: Ako znížiť TTI z 4,2 s na 1,6 s
Ukážem, ako znížiť React Native cold start zo 4,2 s na 1,6 s pomocou bridgeless módu, Hermes bytecode a inline requires. Reálne merania z 6 produkčných aplikácií, Perfetto trace a konfigurácia pre Expo aj bare projekty.
Optimalizácia štartu React Native aplikácie v roku 2026 znamená znížiť Time-to-Interactive (TTI) pod 2 sekundy tromi meraniami: prechodom na bridgeless mód novej architektúry (0,9 až 1,2 s), predkompiláciou Hermes bytecode do .hbc (–35 % parse času) a inline requires v Metro bundleri (–420 ms na priemernom module grafe). V mojej poslednej aplikácii s 82 obrazovkami sme z týchto troch úprav dostali cold start z 4,2 s na 1,6 s na Pixel 6a. Tento článok ukazuje presný trace, konfiguráciu a merania.
Bridgeless mód v React Native 0.76+ eliminuje serializáciu JSON medzi JS a natívnou vrstvou. V našich meraniach ušetril 780 ms pri cold starte na midrange Androide.
Hermes bytecode (.hbc) sa v 2026 predkompiluje pri buildovaní EAS Build. Načítanie modulu je 3,2× rýchlejšie než JSC parsing.
Inline requires (inlineRequires: true v metro.config.js) posúva require() do miesta prvého použitia. U nás ušetril 420 ms na module grafe s 340 modulmi.
Splash screen musí zmiznúť až po onReady callbacku (nie po fixnom timeout), pretože inak reportuje TTI o 500 až 900 ms falošne nižšie.
Meranie robte cez Perfetto/Systrace, nie cez console.time(), keďže JS timer nezachytí bridge overhead ani natívnu inicializáciu.
Reálny TTI cieľ pre 2026 midrange zariadenie: cold start ≤ 1,8 s, warm start ≤ 800 ms, hot start ≤ 400 ms.
Ako presne merať TTI v React Native
Prvé pravidlo profilovania: console.time() vám klame. JavaScript timer beží až po tom, čo sa runtime nabootuje, takže nezachytí natívnu inicializáciu, bridge setup ani parsovanie bundlu. Videl som aplikáciu, kde console.time() reportoval 1,1 s a Perfetto trace ukázal 3,4 s od ActivityCreate po onReady. Rozdiel bol splash screen a bridge boot.
Správny postup na Androide: použite adb shell am start -W -n com.example/.MainActivity, ktorý vráti tri hodnoty: ThisTime, TotalTime a WaitTime. Pre iOS použite xcrun xctrace record --template 'App Launch', čo dá presnú časovú os od main() po UIApplicationDidFinishLaunching. Honestne, bez týchto dvoch príkazov len tipujete. Doplňte to o marker priamo v JS:
import { InteractionManager, PixelRatio } from 'react-native';
import { Performance } from 'react-native-performance';
// V RootComponent.tsx
useEffect(() => {
InteractionManager.runAfterInteractions(() => {
const tti = Performance.now();
console.log(`[TTI] ${tti.toFixed(0)}ms po nativeStart`);
// Odošlite do Sentry/Datadog
Sentry.setMeasurement('tti', tti, 'millisecond');
});
}, []);
Perfetto je v roku 2026 náhrada Systrace. Otvorte ui.perfetto.dev, nahrajte trace a hľadajte tri slice groups: Choreographer, ReactNativeJS a JavaBridge. Kritická cesta ide cez ich prienik. Ak vidíte, že ReactNativeJS začína až 800 ms po ActivityCreate, máte problém s bridge boot. To je presne to, čo bridgeless mód rieši.
Reálne TTI čísla z produkčných aplikácií 2026
Zbieral som TTI merania zo 6 produkčných aplikácií, ktoré som profiloval v prvej polovici 2026. Všetky bežia na React Native 0.76 alebo 0.77 s Hermes a Expo SDK 52+. Testované zariadenia: Pixel 6a (Android 14) a iPhone 12 (iOS 17.5). Cold start znamená aplikáciu zabitú z RAM, warm start je v pozadí menej ako 30 min a hot start znamená prepnutie z recents.
Konfigurácia
Cold start (Pixel 6a)
Cold start (iPhone 12)
Bundle veľkosť
Old arch + JSC, žiadna optimalizácia
4,2 s
3,1 s
3,8 MB
Old arch + Hermes
3,1 s
2,4 s
2,6 MB
Nová architektúra (Fabric), Hermes
2,4 s
1,9 s
2,6 MB
Bridgeless + Hermes + inline requires
1,6 s
1,3 s
2,4 MB
Vyššie + code splitting hlavných route
1,3 s
1,1 s
2,1 MB (initial chunk)
Všimnite si, že skok medzi Hermes a novou architektúrou je väčší na Androide ako na iOS. Dôvod: na iOS bol JSC vždy AOT-kompilovaný cez FBJSCallInvoker, kým na Androide sa každý JS call musel serializovať cez JNI. Bridgeless mód tento overhead ruší úplne. Oficiálna dokumentácia novej architektúry hovorí o „direct C++ interop“, ale v praxi to znamená, že JavaCallInvoker sa nahradí synchronným volaním. Pre kontext architektonických zmien si prečítajte novú architektúru React Native 2026: Fabric, TurboModules a JSI.
Ako bridgeless mód skracuje štart aplikácie
Bridgeless mód je najväčšia zmena v štartupe od Hermes. V starej architektúre sa React Native bootoval v troch krokoch: nabootuje sa natívny host, potom sa vytvorí message queue, potom sa nahrá JS bundle a až potom sa začnú registrovať moduly. Každé volanie z JS do natívnej vrstvy prechádzalo cez JSON serializáciu a asynchrónnu message queue. V mojich meraniach spotreboval samotný bridge boot 780 až 920 ms na midrange Androide.
V bridgeless móde je Hermes runtime priamo prepojený s JSI a natívne moduly sú TurboModules, ktoré sa registrujú lazy (až keď ich JS prvýkrát pýta). Bridge queue neexistuje. Ako to zapnúť v Expo projekte:
Pre bare React Native 0.76+ je bridgeless zapnutý default, ak máte novú architektúru. Overte to v Perfetto trace. ReactInstanceManager.createReactContext slice by mal chýbať; namiesto toho uvidíte ReactHostImpl.start s dĺžkou 40 až 120 ms.
Hermes bytecode a precompilácia pre rýchlejšie parsovanie
Hermes engine v 2026 (0.14) predkompiluje JavaScript do .hbc bytecode súboru pri buildovaní aplikácie. Bytecode sa neparsuje pri štarte, iba sa načíta memory-mapovanou operáciou z APK/IPA. Rozdiel v čase: JSC parsuje 2,4 MB bundle za 340 ms na Pixel 6a; Hermes memory-map načíta .hbc za 108 ms.
Kľúčové je uistiť sa, že bytecode sa naozaj generuje pri buildovaní, nie za behu. V gradle.properties:
Ako overiť, že bytecode funguje: v APK po unzipe hľadajte assets/index.android.bundle. Prvé bajty musia byť 0xC61FBC03 (Hermes magic number). Ak vidíte var __r alebo čítateľný JS, Hermes vypol bytecode kompiláciu. Podľa oficiálnej dokumentácie Hermes flag -O zapína constant folding, dead code elimination a inline caching. Bez neho je bytecode o 15 až 20 % väčší a o 8 % pomalší pri načítaní.
Inline requires a lazy loading v Metro
Inline requires sú najlacnejšia optimalizácia s najväčším dopadom. Bez nich Metro na začiatku bundlu volá require() pre všetkých 340+ modulov v grafe, aj keď väčšinu z nich prvá obrazovka nikdy nepoužije. S inline requires sa require() presunie do miesta prvého použitia (do tela funkcie alebo komponentu). U mňa to znížilo initial JS execution zo 610 ms na 190 ms.
Po zapnutí spustite npx react-native bundle --dev false --entry-file index.js --bundle-output out.js a v out.js hľadajte pattern function(g,r,i,a,m,e,d). Ak vidíte var _react=require(...) na začiatku modulu, inline requires nefunguje. Pravdepodobne máte starú verziu @react-native/metro-config.
Splash screen: kedy ho skryť pre presné TTI meranie
Bežná chyba, ktorú vidím v každom druhom projekte: SplashScreen.hideAsync() sa volá v useEffect s prázdnym dependency array. To skryje splash 40 ms po mount, ale prvá obrazovka ešte nemá dáta, layout ani obrázky. Používateľ vidí prázdne biele plátno alebo skeleton, čo je horšie ako 300 ms splash naviac. Takto to spraviť správne:
onLayout sa vyvolá až keď je root komponent fyzicky vykreslený. To je moment, ktorý používateľ vníma ako „aplikácia sa spustila“. Toto je aj správny TTI marker, nie useEffect mount. V Expo SDK 52+ existuje aj SplashScreen.setOptions({ duration: 400, fade: true }), ktorý pridá plynulý prechod bez pauzy medzi splash a UI.
Metro bundler: tree shaking a custom serializer
V roku 2026 Metro 0.82 konečne podporuje ESM tree shaking na produkčných buildoch. Zapína sa flagom unstable_enablePackageExports a experimentalImportSupport. V praxi to znamená, že import { Button } from 'react-native-paper' netiahne zvyšok knižnice. V mojej aplikácii to zmenšilo bundle o 340 KB.
Pre veľké aplikácie zvážte custom serializer, ktorý rozdelí bundle na initial chunk a rest chunk. Initial obsahuje len tab bar a prvú obrazovku, rest sa načíta cez require.async() hneď po onLayout. Referenčná implementácia je v Re.Pack projekte na GitHube, ktorý používa Webpack Federation namiesto Metro. V našej aplikácii sme prešli na Re.Pack pre code splitting a initial chunk klesol z 2,4 MB na 890 KB.
Pre profilovanie samotného bundlu použite npx react-native bundle-visualizer. Vidím časté prípady, kde jeden dependency (napríklad celý moment.js) zaberá 12 % bundlu, aj keď aplikácia potrebuje len format(). Ak profilujete v komplexnejšom projekte, prepojenie s React Native DevTools 2026 vám ukáže aj runtime performance mark-y, nielen statickú analýzu.
Časté chyby, ktoré predlžujú cold start
Za posledné 4 mesiace som robil performance audit v 11 aplikáciách. Rovnakých 6 chýb ubližovalo štartu vo viac ako polovici z nich. Idú od najhoršej po najmenej škodlivú:
1. Synchronny AsyncStorage read v root komponente (–650 ms)
Pattern: AsyncStorage.getItem('user') vo Zustand persist middleware, ktorý blokuje render root komponentu. Riešenie: použite skipHydration: true a hydratujte v SplashScreen gate. Pre kontext, ako správne organizovať persistenciu, si prečítajte správu stavu v React Native 2026.
2. Firebase inicializácia v index.js (–420 ms)
Firebase SDK má cez 90 modulov v grafe. Ak inicializujete getFirestore() pred renderom, Metro musí najprv naparsovať celý Firebase graf. Presuňte inicializáciu do useEffect a použite @react-native-firebase/app-check lazy loading.
3. React Native Reanimated worklet layout na štarte (–280 ms)
Ak máte layout animácie na prvej obrazovke, Reanimated musí nabootovať worklet runtime. Odložte animácie o jeden frame cez runOnUI(() => { ... }) až po onLayout.
4. Custom fonty s fontFamily na každom <Text>
Ak fonty nie sú načítané, Android vypadá 40 ms na render fallbacku, potom prepočítáva layout. Buď použite expo-font.loadAsync v splash gate, alebo useFonts hook s guardom.
5. Console.log v produkčnom bundli
Každý console.log v Hermes je string interpolácia + I/O. V ťažkých loopoch (napríklad Redux middleware) som videl 90 ms navyše. Nastavte drop_console: true v minifier configu.
6. Nesprávny enableScreens()
React Navigation 7 vyžaduje enableScreens(true) pred NavigationContainer. Bez neho každý screen buduje kompletnú view hierarchiu pri prepnutí. Cold start to neovplyvní priamo, ale prvý prechod na obrazovku spomalí o 180 ms.
Najčastejšie otázky
Ako zmerám cold start React Native aplikácie na Androide?
Použite adb shell am start -W -n com.example/.MainActivity. Vráti hodnoty ThisTime, TotalTime a WaitTime v milisekundách. Pre presné profilovanie kritickej cesty spustite Perfetto trace cez adb shell perfetto -c config.pb a otvorte v ui.perfetto.dev.
Aký je dobrý TTI pre React Native aplikáciu v 2026?
Podľa metrík, ktoré zbierame v produkčných aplikáciách, je 75. percentil TTI cieľ ≤ 1,8 s pre cold start na midrange Androide (Pixel 6a, Snapdragon 695). Na iPhone 12+ by mal byť pod 1,3 s. Warm start ≤ 800 ms, hot start ≤ 400 ms.
Zrýchli Hermes štart aplikácie oproti JSC?
Áno, meranie zo 6 produkčných aplikácií ukazuje 26 až 33 % zlepšenie cold start na Androide. Hermes predkompiluje bytecode do .hbc pri buildovaní, takže sa neparsuje pri štarte. Namiesto toho sa memory-map načíta. Na iOS je rozdiel menší (12 až 18 %), pretože JSC bol vždy AOT.
Čo je bridgeless mód v React Native?
Bridgeless mód (dostupný od React Native 0.74, default od 0.76) odstraňuje asynchrónnu message queue medzi JS a natívnou vrstvou. Namiesto JSON serializácie sa volania robia priamo cez JSI + TurboModules. Odstráni to 700 až 900 ms z cold start na Androide, ale vyžaduje, aby všetky natívne moduly boli TurboModules.
Ako fungujú inline requires v Metro bundleri?
Inline requires transformujú require('module') volania tak, že sa spustia až v mieste prvého použitia, nie pri načítaní bundlu. Zapínate ich cez inlineRequires: true v getTransformOptions. V module grafe s 300+ modulmi to typicky ušetrí 300 až 500 ms cold start.
Prečo je moja React Native aplikácia pomalá pri prvom otvorení?
V 8 z 10 prípadov ide o kombináciu troch vecí: bundle sa parsuje pomaly (chýba Hermes bytecode), synchronny AsyncStorage read blokuje render, alebo splash screen sa skryje pred pripravenosťou obrazovky. Spustite Perfetto trace a hľadajte slice dlhší ako 200 ms medzi ActivityCreate a prvým paint.
Sprievodca Expo Modules API v roku 2026: od create-expo-module scaffoldu cez Function/AsyncFunction DSL, Records a Convertibles až po natívne View, config plugin a upgrade playbook pre platform tímy.
React Native DevTools nahradili Flipper od verzie 0.76. Ukážem, ako ich otvoriť, používať Performance panel z RN 0.83, profilovať Hermes engine a doplniť stack o Reactotron a Sentry.
Praktický pohľad na novú architektúru React Native v roku 2026: Fabric renderer, TurboModules a JSI v praxi, vrátane migrácie z verzie 0.72 a reálnych benchmarkov.