◉RORK LABJP
●EXPO — EAS Observe now records native crashes (Oct 7). On SDK 57, update to 57.0.21 or later●SDK 58 — SDK 58 Beta has been out since Sep 15. The stable date is still unconfirmed●RN 0.88 — React Native 0.88.x is scheduled for Oct 12, 3 days left●Q&A — People are asking why expo-widgets render blank only in production builds●RORK — GPT-6.1 Sol was added on Sep 29, available on Pro and Max plans●NEW — Building an app for a client? Decide who owns the publishing account first●EXPO — EAS Observe now records native crashes (Oct 7). On SDK 57, update to 57.0.21 or later●SDK 58 — SDK 58 Beta has been out since Sep 15. The stable date is still unconfirmed●RN 0.88 — React Native 0.88.x is scheduled for Oct 12, 3 days left●Q&A — People are asking why expo-widgets render blank only in production builds●RORK — GPT-6.1 Sol was added on Sep 29, available on Pro and Max plans●NEW — Building an app for a client? Decide who owns the publishing account first
Articles/Dev Tools
⬡ Dev Tools/2026-06-27Intermediate

When Your App's Launch Got Quietly Slower Release After Release — Field Notes on TTI Instrumentation and a Startup Budget for Rork Apps

Rork apps tend to start fast on day one, then drift slower as you add features and SDKs. These field notes show how to catch cold-start regressions that averages hide, trace them to a release, and stop the drift with a CI startup budget.

Rork577performance11cold startTTIReact Native238

✦ Premium Article

A freshly shipped app usually launches with a satisfying snap. The trouble shows up six months later. As you add features, pile on SDKs, and grow the number of screens, the launch quietly slides into "kind of sluggish." Ask which release caused it and you can't say — each individual regression is too small for anyone to notice on the day it lands.

I've run a wallpaper app solo for a long time, and launch sluggishness erodes review scores and pushes up uninstalls in a slow, grinding way. After I added an ad SDK and analytics, reviews started mentioning "feels laggy lately," and I burned a few days unable to pin the cause on any single thing. These notes are the operational record of how I made that kind of mystery regression traceable with numbers instead of gut feel. They assume an app built with Rork (React Native / Expo output).

The average hides the regression

The first habit to drop is looking at launch time as a single average. Averages get dragged toward fast devices and warm caches, so they report a number rosier than what people actually feel.

What you want is launch split three ways.

TypeDefinitionWhy watch it
Cold startLaunch with no existing processSets the first impression for new users and after reboots. Slowest and most important
Warm startProcess alive, UI rebuiltExposes the weight of SDK init and route construction
Hot startReturning from backgroundIf slow here, suspect listeners and re-renders

And look at p75 and p95, not the mean. The complaints about lag come from the slow quarter. Then split by device. If you only watch the newest iPhone, you'll miss regressions on the few-year-old mid-range Android that much of your real audience runs. I keep one slowest physical device fixed for testing and treat its cold-start p75 as my baseline.

Measure "when can the user act"

The metric worth anchoring on is TTI (Time To Interactive): not the moment the splash disappears, but the moment the user can actually perform their first action. Showing a longer splash only makes launch look fast; if it still takes a while to become interactive, the felt experience hasn't improved.

Measure it in two layers: a native "app start trace" from process start, and a JS-side "TTI mark" for when the first screen is genuinely usable.

Measuring the native trace precisely by hand is painful, so the realistic move is to let Firebase Performance's automatic _app_start trace or Sentry's mobile App Start measurement handle it. You can bolt either onto the Expo project Rork generated.

The JS-side TTI mark records once, when the first interactive screen finishes mounting. react-native-performance makes this easy to express off performance.now().

// At the very top of the root, e.g. app/_layout.tsx
import performance from 'react-native-performance';
import { useEffect, useRef } from 'react';
 
// Place the mark at the earliest point of module evaluation
performance.mark('jsModuleStart');
 
export function useReportTTI(screenName: string) {
  const reported = useRef(false);
 
  useEffect(() => {
    // Right after the first frame paints and becomes interactive
    if (reported.current) return;
    reported.current = true;
 
    performance.mark('firstScreenInteractive');
    const tti = performance.measure(
      'tti',
      'jsModuleStart',
      'firstScreenInteractive'
    );
 
    // Send to analytics. Always attach the release version
    reportMetric('tti_ms', Math.round(tti.duration), {
      screen: screenName,
      appVersion: APP_VERSION,
      coldStart: wasColdStart(),
    });
  }, [screenName]);
}

The crucial part is attaching appVersion to every sample. Without it, you can't later separate "which release got slow." Investigating launch regressions ultimately lives or dies on whether that link exists.

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦How to catch launch regressions using cold-start p75 on a slow device instead of a misleading average
✦Tying a native app-start trace and a JS TTI mark to the release so you can find where the regression came from
✦A CI startup budget that fails the PR before a quiet slowdown ships
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $15 for lifetime access
View Membership →

Related Articles

⬡ Dev Tools2026-09-04
EAS secret visibility does not keep a value out of your app — deciding prefix and visibility separately
The EXPO_PUBLIC_ prefix decides what ships inside your app; EAS visibility decides who can read it. Why stacking them blanks a value on OTA updates, and how to check your build.
⬡ Dev Tools2026-08-22
Every bulk replace exited zero. The damage was in the lines I did not delete
Run a bulk replace over generated code and the breakage lands on the neighbouring lines, not the matched ones. Here is what broke in a live project, and a dependency-free guard that checks the invariants a replace must preserve.
⬡ Dev Tools2026-08-14
Find the native edits expo prebuild will erase before you upgrade to SDK 57
Expo SDK 57 makes expo prebuild clear and regenerate ios and android by default. Here is how to audit your hand edits first, move them into config plugins, and why 57.0.9 matters for Reanimated apps.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links