Serhii.Get in touch
Back to work
HealthTech · TenThousand2022 — 2023React Native Developer

RN cold-start −25% on a HealthTech app

A medically compliant heart-rate tracking app where the first reading needed to land within a strict TTI budget. We brought cold-start from 1.8s to 1.35s and cut state boilerplate by 40% on the way.

−25%
cold-start TTI
−40%
state boilerplate
10+
screens on extracted hook lib
React NativeTypeScriptMobXHermesMetro

The problem

The product was a heart-rate tracking app for a HealthTech company. Medical-grade UX has a non-negotiable rule: the first sensor reading must appear within a tight TTI budget after launch — otherwise users literally distrust the device and uninstall.

On the legacy build, cold-start from a fully-killed state averaged 1.8s on mid-range Androidand ~1.4s on iOS. Drop-off before the first reading screen rendered was measurable. Worse, the team had accumulated 100+ deprecation warnings across two RN minor versions without migrating, and Hermes wasn't enabled on the Android build.

What I looked at first

Before touching any code, I traced one cold launch end-to-end with Systrace on Android and Instruments on iOS. The breakdown was eye-opening:

  • Native init: 320ms
  • JS bundle parse (JSC, no Hermes): 540ms ← biggest single block
  • Bridge init + module registration: 280ms
  • JS execute → root render: 460ms
  • Heart-rate service init: 200ms (blocking the render!)

Two clear targets jumped out: enable Hermes (kills the parse cost), and defer the heart-rate service init off the critical path.

The migration

We were two RN minors behind. Skipping a version just to cherry-pick Hermes wasn't safe — the dependency tree had broken peer ranges. I planned the migration as three sequential, testable steps:

  1. Bump RN minor 1 with all Android/iOS native code regen, fix deprecations in RCTBridge usage and old useNativeDriver patterns.
  2. Bump RN minor 2 alongside enabling Hermes on Android. This forced fixes in 4 third-party libs that were calling JSC-specific globals.
  3. Defer non-critical native modules — analytics, crash reporting, deep-link handler — to register after the first paint via aInteractionManager.runAfterInteractions wrapper.
Heads up
Hermes broke our existing console.log-based perf assertions because it formats numbers slightly differently in stringified objects. Tests had to be rewritten with a real comparison harness — which was a good thing in retrospect.

Redux → MobX, and what it actually saved

Mid-migration we discovered the heart-rate stream was driving a Redux store with ~30 actions per second during a measurement. Each action cycled through middleware, sagas, and a deep combineReducers tree. The store wasn't the bottleneck for cold-start specifically, but it was a death-by-a-thousand-cuts problem for measurement screens.

I moved the streaming domain to MobX observables. The heart-rate service writes directly to observable atoms; React components read via observer() and re-render granularly without going through any global dispatch.

The cleanup was real: −40% linesacross the affected modules, mostly action creators, action types, reducer cases, and saga plumbing that didn't need to exist.

The win wasn't MobX-vs-Redux. It was that streaming domain logic belongs near the source of truth, not in a global pipeline.

Hook library extraction

While moving screens to MobX, I noticed the same five primitives kept showing up: useDebounced, useInterval with background-pause, useFocusedTimer, useFormValidation, and a typed useMeasurement for the heart-rate service.

I extracted them into an internal @app/hooks package with full type-safety and unit tests. By the end of the project, that package was used across 10+ screens, and per-feature dev time on new sensor screens dropped noticeably.

The result

  • Cold-start TTI: 1.8s → 1.35s on the 50th-percentile Android device (−25%)
  • State boilerplate: −40% across the streaming domain, measured by lines-of-code-removed minus added
  • Hook library reused on 10+ screens, became the template pattern for the next two sensor integrations
  • 0 deprecation warnings after migration, with a CI guard preventing regressions

What I'd do differently

I'd run the Hermes flag-flip in production behind a remote config for the first 48 hours. We rolled it out global-on-merge and caught one crash in a third-party lib that only manifested on Android 8 — a rollback would've been faster than a hotfix.

I'd also have written the perf assertions before the migration, not during. The temptation to start coding immediately is always strongest when the metric is obvious.

Next case study

CI/CD: 15 min → under 5 min

SaaS · SolidWay · 2023 — Present