All skills

Complete React Native and Expo optimization guide combining Callstack profiling with Vercel code patterns. Covers FPS, TTI, bundle size, memory, lists, animations, state, UI, and React Compiler. Use for any React Native performance, debugging, or best practices task.

Use this Skill: https://skilld.dev/gh/gigs-slc/react-native-skills/react-native

This session only. Nothing lands on disk.

referencesnative-measure-tti.md

≈1.7k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Skill: Measure TTI (Time to Interactive)

Set up performance markers to measure app startup time and track TTI improvements.

Quick Command

npm install react-native-performance
// Mark when screen is interactive
import performance from 'react-native-performance';

useEffect(() => {
  performance.mark('screenInteractive');
}, []);

When to Use

  • App startup feels slow
  • Need baseline metrics for optimization
  • Setting up performance monitoring
  • Comparing TTI across releases

Prerequisites

  • react-native-performance library (recommended)
npm install react-native-performance

Note: This skill involves interpreting visual timeline diagrams and profiler output. AI agents cannot yet process screenshots autonomously. Use this as a guide while reviewing metrics manually, or await MCP-based visual feedback integration (see roadmap).

Understanding TTI

Time to Interactive: Time from app icon tap to displaying usable content.

Startup Types

Type Description Measure?
Cold App not in memory, full init ✅ Yes
Warm Process exists, activity recreated ❌ Skip
Hot App in background, resumed ❌ Skip
Prewarmed (iOS) iOS pre-initialized app ❌ Filter out

Only measure cold starts for consistent metrics.

React Native Startup Pipeline

TTI Warm Start Diagram

The diagram shows a warm start (app was in memory):

UI Thread:

  1. init native process → init native app
  2. Gap while user is away (e.g., "5h break from using the app")
  3. JS bundle load → RootView render

JS Thread (runs in parallel):

  • init entrypoint → registerComponent

Pipeline markers:

1. Native Process Init     (nativeLaunchStart → nativeLaunchEnd)
2. Native App Init         (appCreationStart → appCreationEnd)  
3. JS Bundle Load          (runJSBundleStart → runJSBundleEnd)
4. RN Root View Render     (contentAppeared)
5. React App Interactive   (screenInteractive) ← This is TTI

Step-by-Step Implementation

1. Detect Cold Start

iOS (Swift):

let isColdStart = ProcessInfo.processInfo.environment["ActivePrewarm"] != "1"

Android (Kotlin):

class MainApplication : Application() {
    var isColdStart = false
    
    override fun onCreate() {
        super.onCreate()
        
        var firstPostEnqueued = true
        Handler().post { firstPostEnqueued = false }
        
        registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
            override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
                unregisterActivityLifecycleCallbacks(this)
                if (firstPostEnqueued && savedInstanceState == null) {
                    isColdStart = true
                }
            }
            // ... other callbacks
        })
    }
}

2. Check Foreground State

Only measure when app starts in foreground.

iOS:

var isForegroundProcess = false

override func application(_ application: UIApplication, 
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    if application.applicationState == .active {
        isForegroundProcess = true
    }
    return true
}

Android:

private fun isForegroundProcess(): Boolean {
    val processInfo = ActivityManager.RunningAppProcessInfo()
    ActivityManager.getMyMemoryState(processInfo)
    return processInfo.importance == IMPORTANCE_FOREGROUND
}

3. Set Up Performance Markers

Using react-native-performance:

Native (iOS):

import ReactNativePerformance

RNPerformance.sharedInstance().mark("appCreationStart")
// ... app init ...
RNPerformance.sharedInstance().mark("appCreationEnd")

Native (Android):

import com.oblador.performance.RNPerformance

RNPerformance.getInstance().mark("appCreationStart")
// ... app init ...
RNPerformance.getInstance().mark("appCreationEnd")

4. Mark Screen Interactive (JavaScript)

import performance from 'react-native-performance';

export default function HomeScreen() {
    useEffect(() => {
        // Mark when meaningful content is displayed
        performance.mark('screenInteractive');
    }, []);
    
    return <TabNavigator />;
}

5. Collect and Report Metrics

import performance from 'react-native-performance';

const collectTTIMetrics = () => {
    const entries = performance.getEntriesByType('mark');
    
    // Calculate durations
    const metrics = {
        nativeInit: getMarkDuration('nativeLaunchStart', 'nativeLaunchEnd'),
        appCreation: getMarkDuration('appCreationStart', 'appCreationEnd'),
        jsBundleLoad: getMarkDuration('runJSBundleStart', 'runJSBundleEnd'),
        tti: getMarkDuration('nativeLaunchStart', 'screenInteractive'),
    };
    
    // Send to analytics
    analytics.track('app_performance', metrics);
};

Built-in Markers

react-native-performance provides automatic markers:

Marker Description
nativeLaunchStart Process start (pre-main)
nativeLaunchEnd Native init complete
runJSBundleStart JS bundle loading starts
runJSBundleEnd JS bundle loaded
contentAppeared RN root view rendered

Listening to Native Events

iOS (JS Bundle Load):

NotificationCenter.default.addObserver(
    self,
    selector: #selector(onJSLoad),
    name: NSNotification.Name("RCTJavaScriptDidLoadNotification"),
    object: nil
)

Android (JS Bundle Load):

ReactMarker.addListener { name ->
    when (name) {
        RUN_JS_BUNDLE_START -> { /* mark start */ }
        RUN_JS_BUNDLE_END -> { /* mark end */ }
        CONTENT_APPEARED -> { /* mark content */ }
    }
}

Target Metrics

Metric Good Acceptable Needs Work
TTI < 2s 2-4s > 4s
JS Bundle Load < 500ms 500ms-1s > 1s
Native Init < 500ms 500ms-1s > 1s

Note: Targets vary by app complexity and device tier.

Common Pitfalls

  • Including prewarmed starts: iOS prewarming skews metrics
  • Measuring warm/hot starts: Only cold starts are meaningful
  • Wrong screenInteractive placement: Mark when truly interactive, not just mounted
  • Not filtering background launches: Push notifications can start app in background

Related Skills

Source: SKILL.md on GitHub

2 alerts6mo4 checks · Risk HIGH
  • Gen Agent Trust Hub7mo

    The skill is a technical React Native guide that includes a high-risk remote code execution pattern for installing a performance tool.

  • Socket6mo

    No alerts

  • Snyk7mo

    Risk: MEDIUM · No issues

  • Runlayer7mo

    7/67 files flagged

Signed by skilld at 1d299ca. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 days ago.

Dormantupdated 8 months ago
Other metadata
metadata
{
  "author": "Callstack + Vercel",
  "version": "1.0.0",
  "tags": "react-native, expo, performance, optimization, patterns"
}

README badge

README badge for gigs-slc/react-native-skills/react-native