A React Native hidegindítás optimalizálás a TTI (Time to Interactive) csökkentését jelenti attól a pillanattól, amikor a felhasználó megnyomja az app ikonját, addig, amíg a JS thread ténylegesen válaszol a bemenetére. 2026-ban a New Architecture (bridgeless mode) plusz Hermes precompilált bytecode és inline requires kombinációja tipikusan 35–55%-kal rövidebb TTI-t hoz egy közepes méretű appban a régi híd alapú beállításhoz képest. Persze csak akkor, ha a natív init sem húzza vissza. Ez az útmutató végigmegy a mérésen, a profilozáson és a konkrét beavatkozásokon egy 2026-os React Native 0.77+ appban.
A hidegindítás három szakaszra bontható: natív init (AppDelegate/MainActivity), JS bundle betöltés és parsing, majd az első React render TTI-ig. Mindhármat külön kell mérni.
A Hermes precompilált bytecode (HBC) tipikusan 30–40%-kal gyorsítja a JS parse fázist a JSC-hez képest; a New Architecture bridgeless módban további 15–25%-ot ad a first-render idejéből.
Az inlineRequires és a lazy import lekapcsolja azt a kódot, amit az első screenhez nem használsz. Egy 4 MB-os bundle esetén 200–400 ms-ot lehet vele visszanyerni Android midrange eszközön.
Mérni a react-native-performance-szel vagy a React Native DevTools Performance panellel érdemes; Systrace/Perfetto Androidon a natív init részletes bontását adja.
Az iOS launchOptions-ben, az Android MainApplication.onCreate-ben minden szinkron init (analytics, crash reporter, feature flags) direktben növeli a hidegindítást. Ezeket InteractionManager.runAfterInteractions mögé kell tenni.
Mit jelent a TTI és hogyan mérjük?
A TTI (Time to Interactive) egy alkalmazásnál az az idő, ami az ikon-tap pillanata és aközött telik el, amíg a JS thread szabadon fut és reagál a felhasználó bemenetére. Ez nem ugyanaz, mint az „első pixel". Az sokszor 300 ms-mal korábban megjelenik (splash screen vagy első React frame), miközben a scroll még akadozik, mert a bundle még parse-olás alatt van.
Egy tavalyi profilozási eset nálam: egy fintech appnál 1.2 másodperces „látszólagos" indulást mutattam, de a valós TTI 2.8 s volt egy Pixel 6a-n, mert a JS thread egy analytics init blockingra várt. Onnantól kezdve mindig három számot kérek le, nem egyet.
Mérni három eszközzel érdemes. Kezdésnek a hivatalos react-native-performance könyvtárral: ez a W3C User Timing API-t implementálja React Native-ben, és a runtime start pillanatát is elkapja. Runtime markereket a PerformanceObserver-rel olvasod ki:
Második: a React Native DevTools Performance panel (0.76+) egy CDP-alapú trace-t készít, amiben látod a JS parse, első render és bridge callok költségét. Harmadik: Androidon a Perfetto (a régi Systrace utódja) natív oldali bontást ad. Így látod, mennyi ideig fut a MainApplication.onCreate, és mikor kezdődik el egyáltalán a JS runtime. Kombinálva ezt a hármat, minden szakaszra van tized-ms pontosságú számod.
A hidegindítás három szakasza
A React Native hidegindítás mindig ugyanabból a három fázisból áll, és mindegyik saját szűk keresztmetszettel rendelkezik. Ha nem tudod, melyik fázis hány ms-ot fogyaszt, csak vaktában optimalizálsz.
1. Natív init (150–600 ms)
Ez az AppDelegate (iOS) vagy MainApplication.onCreate (Android) lefutásának ideje: itt regisztrálódnak a natív modulok, itt jön létre a bridge (vagy bridgeless módban a runtime scheduler), és itt fut minden third-party SDK init, amit szinkronban tettél az app életciklusba. Firebase, Sentry, Segment, feature flag SDK-k mind ide szoktak beúszni, és mindegyik 20–80 ms-ot vihet Android midrange eszközön.
2. JS bundle load és parse (300–1200 ms)
A bundle beolvasása diszkről (Metro-produced index.android.bundle vagy Hermes HBC), majd a Hermes/JSC engine parse-olja és evaluálja a top-level modulokat. Egy 3 MB-os JS bundle Hermes-szel Android midrange-en tipikusan 400–500 ms. Ha még JSC-t használsz iOS-en, számolj 30–50%-os felárral az iPhone SE (2020) osztályú eszközökön.
3. Első React render és TTI (100–500 ms)
A root komponens fut, meghívja a lazy screen-t (ha van), lefut a useEffect-ek első köre, és amikor a JS thread végre 50 ms-nál rövidebb ideig blokkolt, azt tekinthetjük TTI-nek. Itt szokott meglepetés lenni: egy „csak egy screen"-nek látszó dashboard sokszor 3–4 párhuzamos fetch-et indít az effect-ekben, és ha a válasz JSON.parse-a 30 KB-nál nagyobb, az a fő szálon blokkol.
Profilozás React Native DevTools és Perfetto segítségével
A profilozási munkafolyamat mindig ugyanaz: futtatod a release buildet (nem dev-et, a Dev módban minden 2–3-szor lassabb), rögzítesz 3–5 hidegindítást, megnézed a mediánt, majd egy változást csinálsz és újramérsz. A React Native DevTools debugolási útmutatómban részletesen leírtam a DevTools setupot; itt a Performance panellel foglalkozunk.
A trace megnyitása után három sávot érdemes nézni: a JS thread (ez a legfontosabb), a UI/native thread, és a Timings sávot, ahol a saját performance.mark-jaidat találod. A JS thread egyetlen 400 ms-os szürke blokkja szinte biztosan a bundle első kiértékelése. Ha ezen belül nagy modulok neveit látod (moment, lodash teljes import, egy nagy screen komponens), az inline require jelölt.
Androidon a Perfetto trace-t így indítod egy release APK-n:
A .pftrace fájlt a ui.perfetto.dev-en nyisd meg. Keresd a Activity#onCreate, ReactApplicationContext#initialize és runJSBundle markereket. Ez a natív és JS init teljes bontása egyetlen képen.
Hermes precompilált bytecode (HBC)
A Hermes 2026-ra az alapértelmezett JS motor (0.70+ óta), és Android és iOS oldalon egyaránt élesben van. A trükkje: build időben előre lefordítja a JS-t egy kompakt bytecode formátumra (HBC, azaz Hermes Bytecode), így runtime-ban a parse fázis szinte nulla. Csak a modulok kiértékelését kell megcsinálnia. Egy 3.5 MB-os JS bundle-ből tipikusan 2.6 MB HBC lesz, és a betöltés+parse ideje Android midrange-en 900 ms-ról 500 ms alá csökken.
2026-ban a Hermes már nemcsak alap, hanem specifikusan a startup-ra optimalizált flag-jei is stabilak. A hermes-flags-be érdemes belőni a -O-t és a -output-source-map-ot production buildben:
Ha a konzol azt mondja, hogy false, de te true-t vársz, valószínűleg a gradle clean és pod install --repo-update hiányzik a switch óta. Egyszer szinte fél napot vesztegettem el ezen a hibán az egyik projektemben, mielőtt eszembe jutott a cache. A hivatalos React Native Hermes dokumentáció teljes ellenőrző listát ad a migrációhoz.
Inline requires és lazy import stratégia
Az inlineRequires a Metro egyik legjobb ár-érték arányú optimalizációja. Alapból a top-level import-ok a bundle első kiértékelésekor mind lefutnak. Ezért van, hogy még ha a felhasználó csak a bejelentkező screen-t látja, a settings, profile, checkout modulok is betöltődnek. Inline requires-szel a Metro a require-ket a használat pillanatára halasztja: első render és TTI szinte azonnal, a nehéz screenek pedig akkor kompletálódnak, amikor navigálsz rájuk.
Egy 4 MB-os bundle-nél nálam ez ~280 ms-os JS init csökkenést hozott egy Pixel 4a-n. Van azonban egy csapda: néhány library (moment locales, egyes i18n loaderek) side-effect-re számít a top-level import-nál. Ha inline requires után váratlan „X is not a function" hibát látsz első használatkor, add hozzá a modul útját a nonInlinedRequires-hez:
A React Navigation-nél még egy plusz kör: használj lazy: true-t a Stack.Screen komponenseken, hogy csak a látott screen mountoljon. Ha a React Navigation v7 TypeScript útmutatóm szerint állítottad be az app struktúrát, a lazy prop a Tab.Navigator-on 2–3 tab esetén tipikusan 60–100 ms-ot spórol az első interaktív állapot előtt.
New Architecture és bridgeless mode hatása a startupra
A New Architecture (Fabric renderer, TurboModules és Bridgeless mode) 2026-ban az alapértelmezett új projektekben (Expo SDK 55, React Native 0.76+). A startup szempontjából a legfontosabb változás a bridgeless mode: a régi híd (bridge) helyett a JS és a natív oldal közvetlenül JSI-n (JavaScript Interface) keresztül kommunikál, ami a init során megspórolja a serialize/deserialize köröket a modul-regisztrációnál.
Számokban: egy közepes appnál (kb. 25 natív modul, 8 Fabric komponens) a bridgeless mode bekapcsolása 120–180 ms-ot vág le a natív init idejéből egy Pixel 6a-n. Ez azért fontos, mert ez az egyetlen fázis, ami előtt a felhasználó semmilyen visszajelzést nem lát a splash screen alatt.
Engedélyezés Expo SDK 55+ projektben (ez már default):
Bare workflow-ban gradle.properties-ben newArchEnabled=true, iOS-en pedig RCT_NEW_ARCH_ENABLED=1 a pod install előtt. Az Expo SDK 55 és New Architecture útmutatóm részletesen leírja a migráció menetét, ha még nem vagy ott.
Natív init optimalizálás iOS és Android oldalon
A natív init az egyetlen olyan fázis, amit nem tudsz JS oldalról befolyásolni. Itt viszont a legkönnyebb elrontani: minden szinkron SDK init közvetlenül nyúlja meg a TTI-t.
Android: MainApplication.onCreate lean tartása
Egy tipikus hiba, hogy analytics, crash reporter, feature flag és push notif init mind a MainApplication.onCreate-ben szinkronban fut. Ezek helyett használj késleltetett inicializálást a first frame után:
// MainApplication.kt
override fun onCreate() {
super.onCreate()
SoLoader.init(this, false)
// Csak amire az első screen render-hez feltétlen szükség van
ReactNativeFeatureFlags.override(ReactNativeNewArchitectureFeatureFlags())
// Minden más: post az UI thread-re, első frame után
Handler(Looper.getMainLooper()).postDelayed({
initAnalytics(this)
initCrashReporter(this)
initFeatureFlags(this)
}, 500) // vagy még jobb: WorkManager background job-ban
}
iOS: AppDelegate didFinishLaunchingWithOptions
Ugyanaz a gondolat Swift-ben. Az application(_:didFinishLaunchingWithOptions:)-ben csak a bridge/runtime init menjen, minden mást DispatchQueue-n aszinkronban:
Egy általam nézett e-commerce appnál ez a két változás önmagában 340 ms-ot vágott le a TTI-ből iPhone 12-n és 480 ms-ot egy Samsung A34-en. A crash reporter aszinkron inicializálása kockázatosnak tűnik (mi lesz, ha crashel a késleltetés ablakban?), de a Sentry, Bugsnag és Crashlytics mindegyik támogatja az attachStacktrace: true-t utólag is. A mérnöki cost/benefit egyértelmű.
Splash screen és a látszólagos TTI
A valós TTI mellett létezik a „látszólagos TTI", vagyis az, amit a felhasználó észlel. Ha a splash screen simán, animációval megy át az első screen-be, a 800 ms is 300 ms-nak érződik. Az expo-splash-screen 2026-ban natívan is támogatja a rejtett-amíg-nem-kész pattern-t:
import * as SplashScreen from 'expo-splash-screen';
import { useEffect, useState, useCallback } from 'react';
SplashScreen.preventAutoHideAsync(); // már az import időpontban
export default function App() {
const [appReady, setAppReady] = useState(false);
useEffect(() => {
(async () => {
try {
await Promise.all([
preloadCriticalAssets(),
restoreAuthSession(),
loadInitialData(),
]);
} finally {
setAppReady(true);
}
})();
}, []);
const onLayout = useCallback(async () => {
if (appReady) await SplashScreen.hideAsync();
}, [appReady]);
if (!appReady) return null;
return <RootNavigator onLayout={onLayout} />;
}
A trükk: az első React render nem jelenti, hogy a screen render-kész. Az adatok, kép preload-ok is kellenek. A splash mögé rejtve ez extra 200–400 ms, amit a felhasználó soha nem lát.
Képek, fontok és more-than-first-frame
Az első frame renderelése önmagában még nem elég. Ha a hero kép és a custom font 500 ms múlva pattannak be, a felhasználó „ugráló" appot lát. Két konkrét fogás:
Font pre-load: az expo-fontuseFonts hookja már natív oldalon várja a font betöltését, mielőtt a JS-t engedné. A splash mögött ez zéró költség, csak akkor lassít, ha a splash már elrejtve.
Kép caching és progressive rendering: az expo-image (nem a régi Image) natív cache-t és placeholder-t támogat. A hero képet a splash mögött cache-eld be:
import { Image } from 'expo-image';
// A splash mögött preload-old le a hero-t
await Image.prefetch([
'https://cdn.example.com/hero.webp',
], 'memory-disk');
// A screen komponensben blurhash placeholder-rel
<Image
source={{ uri: heroUri }}
placeholder={{ blurhash: 'L6PZfSi_.AyE_3t7t7R**0o#DgR4' }}
transition={200}
contentFit="cover"
style={styles.hero}
/>
A blurhash 20-30 karakter, a JS bundle-ba beleégetheted (a hero képekhez), és a felhasználó nem lát fehér lyukat egy pillanatra sem. Ha listát renderelsz első screen-en, a FlashList v2 vs FlatList útmutatómban tárgyalt estimatedItemSize és drawDistance beállítások szintén hozzájárulnak a smooth-nak érzett indításhoz.
Gyakori hibák, amikkel visszalassítod magad
Sorozatosan találkozom ezekkel a hibákkal auditokon, és mindegyik 100–400 ms-ot tud kóstálni:
Top-level side-effect import-ok: import 'sideeffect-lib/init' a root App.tsx tetején. Ez inline requires-t kikerüli, halasszd a hookba.
Nagy JSON konstansok import-olása: import cities from './cities.json' egy 800 KB-os fájllal. Töltsd le on-demand vagy tedd async storage-ba, ne a bundle-ba.
Moment.js teljes locale betöltés: import 'moment/min/moment-with-locales' egyedül ~400 KB. Vagy csak a szükséges locale-t importáld, vagy váltsd le date-fns-re (tree-shakable).
console.log-ok release-ben: minden console.log egy runtime string konkatenációt és bridge callt indít el. Babel plugin transform-remove-console-lel takarítsd el production-ben.
Redux Persist rehydrate blokkoló renderrel: a PersistGate loading={null}-lel ez egy fehér képernyőt jelent 100-200 ms-ig. Vagy renderelj alá egy skeleton-t, vagy migráld át MMKV-ra (nagyságrendekkel gyorsabb, mint AsyncStorage).
Fejlesztői eszközök hagyása release-ben: React DevTools connector, Reactotron kliens, Flipper maradványok. Ellenőrizd, hogy __DEV__ guard mögött vannak.
És végül a legfontosabb: ne állítsd le a mérést az első „úgy tűnik, gyorsabb" pillanatban. A percepcio megcsalja az embert, a stopper nem. Minden változás után 5–10 hidegindítás medián, ugyanazon eszközön, ugyanazon build variantnál. Ez az egyetlen módszer, amivel nem magadnak hazudsz.
Gyakran Ismételt Kérdések
Mi a jó React Native cold start idő 2026-ban?
Egy közepes komplexitású appnál Pixel 6a vagy iPhone 12 osztályú eszközön 1.2–1.8 másodperc TTI reális célszám release buildben, Hermes plusz New Architecture plusz inline requires beállítással. Alacsonyabb kategóriás Android eszközön (Samsung A13, kevesebb RAM) reálisan 2–2.5 s, e-commerce/social osztályú appoknál. 3 másodperc fölött már mérhetően csökken a retention az első munkamenetben.
Segít a Hermes minden esetben a startup-on?
Igen, Hermes vs JSC összehasonlításban a startup idő 25–45%-kal jobb Android midrange eszközön, és iOS-en is 15–25% előnye van. Az egyetlen kivétel, amikor a bundle nagyon kicsi (<500 KB), mert ott a JSC lazy compilation-je hasonlóan gyors. Minden production appnál Hermes az alapértelmezett választás 2026-ban.
Mennyit gyorsít az inline requires egy appon?
A tapasztalatom szerint egy 3–5 MB-os bundle-nél 150–400 ms TTI csökkenést hoz Android midrange eszközön. Minél több screen és minél nagyobb a bundle, annál nagyobb a nyereség. Kis appokban (<1 MB bundle) a különbség 30–50 ms alatt van.
Kell New Architecture-re migrálni a startup gyorsításához?
Nem kötelező, de érdemes: a bridgeless mode 120–180 ms natív init nyereséget hoz egy közepes appban. Ha a projekted még 0.72 alatt van vagy nagy legacy natív modulokkal dolgozik, a Hermes plusz inline requires kombinációval is elérhetsz jó eredményt. Új projekteknél viszont már default a New Arch, nincs értelme letiltani.
Hogyan mérem a hidegindítás időt automatizáltan CI-ben?
Használhatod a adb shell am start -W parancsot Androidon, ami a TotalTime és WaitTime értékeket adja vissza a launch-ra. iOS-en az Xcode Metrics API vagy az XCTest performance measure blokkjai. Maestro flow-ban expect visible-lal is mérhetsz saját TTI markert, és a build pipeline-ban regressziót jelenthetsz, ha 10%-nál többet nő.
A React Native DevTools a 0.76 óta a hivatalos, Chrome DevTools alapú debugger, ami leváltotta a Flippert. Így indítsd, így használd fizikai eszközön is 2026-ban.
A FlashList v2 az Új Architektúrához készült, becsléseket már nem kér, és a legtöbb hosszú listánál lekörözi a FlatList-et. Íme mikor éri meg váltani és mikor nem.
Az EAS Update OTA frissítésekkel másodpercek alatt küldhetsz JS bundle-t React Native app-nak store review nélkül. Csatornák, rollout és code signing 2026-ban.