← Back to list

How I Evolved My React Native Performance Debugging — and How I Measure Performance Optimizations…

This is one of the approaches I use to debug performance in React Native, especially when performance issues become difficult to reason…

Karimhekal · 2025-12-24 14:45 · 1 claps · 7.7 min read
#react-native #react-native-performance #mobile-app-performance #devtools #react-devtools
Open on Medium ↗
Wiki topics: 💻 · Programming 🌐 · Web Development 📱 · Mobile Development

How I Took My React Native Performance optimization to next level with AI assistance

This is one of the approaches I use to debug performance in React Native, especially when performance issues become difficult to reason about as the app grows and saved me alot of time analyzing profiling data.

The Usual Way: Manual React DevTools Profiling

Like most developers, my performance profiling debugging used to follow the common flow:

  1. Open React DevTools
  2. Enable the Profiler
  3. Reproduce the laggy or slow scenario
  4. Stop profiling
  5. Manually inspect timelines, yellow bars, and component trees
  6. Try to reason about why certain components re-render

This approach works — but it doesn’t scale well.

As apps grow:

  • Profiling sessions become large
  • A lot of time goes into analyzing the data
  • Prioritization becomes unclear

The problem isn’t lack of data — it’s too much context.

Export Profiler Data in a huge json file

When a profiling session ends, React DevTools allows you to export the results as a profiling.json file.

Press Save profile to export a .json file

Press Save profile to export a .json file

the file itself

the file itself

This file is the raw output of the profiling session — and in real production scenarios, it can easily contain up to 900,000+ lines of JSON.

At this scale:

  • The file is impossible to reason about manually
  • Feeding it directly to AI is inefficient and error-prone
  • Important performance signals are buried under massive amounts of noise

This is the exact problem the script is designed to solve.

The Core Idea: Minimize Context, Maximize Signal

Instead of manually inspecting everything (or feeding massive profiler data to AI), I changed the goal entirely: I wrote a python script to

Reduce profiling data to the smallest possible context that still captures the real performance problem.

This is where a lightweight analysis script comes in.

Let a Script Structure the Data

The purpose of the script is not to optimize performance.

Its purpose is to:

  • Filter out noise
  • Aggregate repeated patterns
  • Rank bottlenecks by impact
  • Produce a compact, high-signal summary

In other words:

The script exists to minimize the required context for AI.

Instead of giving AI a profiling.json file with 200k+ lines of raw profiler output, AI only sees:

  • Slow / very slow / critical renders
  • Most expensive components (total + max render time)
  • Re-render hotspots and frequent updaters
  • Clear prioritization (ranked bottlenecks by impact)

But how do we make this script? I gave AI a sample of the porifling json to understand the structure of how it’s generated and let it make a script based on it

The script file

The script file

Categorized renderes

Categorized renderes

All i need to do is run :

python3 ./analyze_performance.py <exported-profiling.json>

Output of script in terminal

Output of script in terminal

So once i run the script it outputs these results in terminal and I provide it to AI so it starts analyzing components once by one in their context

The AI-Assisted Workflow

  1. Profile the app using React DevTools
  2. Export the profiling session output as profiling.json
  3. Run the analysis script on that exported file
  4. Generate a structured performance report
  5. Feed only the report output to AI
  6. Apply targeted optimizations

AI never touches raw profiler data — it only reasons about structured insight.

What the Script Extracts (High Signal Summary)

The script focuses on extracting the information that actually helps with decisions:

  • Categorizes renders by severity (example thresholds):
  • Slow (≥16ms): dropped frames at 60fps
  • Very slow (≥50ms): noticeable lag
  • Critical (≥100ms): UI-blocking renders
  • Aggregates per-component metrics:
  • render count
  • total render time
  • average render time
  • max render time
  • Highlights
  • worst commits (by duration)
  • most expensive components (by total time)
  • frequent updaters (high re-render count)
  • slowest individual component renders

This transforms raw profiler output into decision-ready information.

Measuring Optimization by numbers (Before vs After)

Because the script output is structured and consistent, optimizations become measurable.

I run the script:

  • Before optimization
  • After optimization

Then compare both profiling json files:

  • average commit duration
  • number of slow / very slow / critical renders
  • total render cost per component
  • percentage improvement across the same scenario

This turns performance work from subjective into objective.

Example: Before vs After Results

Why This Matters

This approach allows me to:

  • Verify that optimizations actually worked
  • Avoid placebo performance fixes
  • Communicate improvements clearly to: (Tech leads - Product managers - Stakeholders)
  • Track performance regressions over time

It turns performance work into something that is measurable, repeatable, and defensible.

Note on Scope and Limitations:

This AI-assisted, profiler-driven approach is not a silver bullet, and it is not the only method you can use to debug performance issues.

What this method excels at is covering a large percentage of real-world performance problems we commonly face in React Native apps, such as:

  • Unnecessary re-renders
  • Over-updating parent components
  • Expensive component trees
  • Missing memoization (React.memo, useMemo, useCallback)
  • Poor list rendering or virtualization
  • Heavy components rendering too frequently

These are issues that often require well-known best practices and repeatable optimization techniques, and this approach helps surface them quickly, clearly, and at scale.

However, there can be deeper performance problems that this method alone will not fully solve, such as:

  • Inefficient algorithms
  • Poor time complexity (e.g. O(n²) work during renders)
  • Expensive synchronous computations on the JS thread
  • Heavy data transformations inside render cycles
  • Native-side bottlenecks or bridge/JSI overhead
  • Large JSON parsing or serialization costs

That said, even in these cases, this approach is still valuable.

While it may not directly fix such issues, it often points very clearly to where the lag originates, allowing deeper investigation to start in the right place instead of guessing.

Conclusion

React DevTools already provides all the raw data needed to understand performance issues in React Native applications. The real challenge is not collecting data — it’s making sense of it at scale.

By introducing a lightweight analysis script as a compression layer, and using AI only after the data has been structured, performance debugging becomes far more effective. Massive profiler outputs are reduced to high-signal insights, optimizations can be validated with real numbers, and decisions are based on evidence instead of intuition.

Once performance improvements are measurable, optimization stops being guesswork and starts being engineering.

Python script i generated to categorize the profiling.json file results

#!/usr/bin/env python3
"""
React Native Performance Profiler Analyzer
Analyzes React DevTools profiling data and extracts critical performance issues
"""

import json
import sys
from collections import defaultdict
from typing import Dict, List, Tuple

# Thresholds for what we consider "critical" (in milliseconds)
SLOW_RENDER_THRESHOLD = 16  # 16ms = one frame at 60fps
VERY_SLOW_RENDER_THRESHOLD = 50  # Very slow render
CRITICAL_RENDER_THRESHOLD = 100  # Critical performance issue

def load_profile_data(filepath: str) -> dict:
    """Load the profiling JSON data"""
    print(f"📊 Loading profiling data from {filepath}...")
    with open(filepath, 'r') as f:
        return json.load(f)

def analyze_commits(data: dict) -> Dict:
    """Analyze commit data for slow renders"""
    results = {
        'total_commits': 0,
        'slow_commits': [],
        'very_slow_commits': [],
        'critical_commits': [],
        'component_stats': defaultdict(lambda: {'count': 0, 'total_duration': 0, 'max_duration': 0})
    }

    for root in data.get('dataForRoots', []):
        for commit in root.get('commitData', []):
            duration = commit.get('duration', 0)
            results['total_commits'] += 1

            # Categorize by severity
            commit_info = {
                'duration': duration,
                'timestamp': commit.get('timestamp', 0),
                'updaters': commit.get('updaters', []),
                'components_affected': len(commit.get('changeDescriptions', []))
            }

            if duration >= CRITICAL_RENDER_THRESHOLD:
                results['critical_commits'].append(commit_info)
            elif duration >= VERY_SLOW_RENDER_THRESHOLD:
                results['very_slow_commits'].append(commit_info)
            elif duration >= SLOW_RENDER_THRESHOLD:
                results['slow_commits'].append(commit_info)

            # Track component-level stats
            for updater in commit.get('updaters', []):
                name = updater.get('displayName', 'Unknown')
                results['component_stats'][name]['count'] += 1
                results['component_stats'][name]['total_duration'] += duration
                results['component_stats'][name]['max_duration'] = max(
                    results['component_stats'][name]['max_duration'],
                    duration
                )

    return results

def analyze_fiber_durations(data: dict) -> List[Tuple[str, float]]:
    """Analyze individual fiber (component) render durations"""
    fiber_map = {}

    # Build fiber ID to name mapping from snapshots
    for root in data.get('dataForRoots', []):
        snapshots = root.get('snapshots', [])
        if isinstance(snapshots, list):
            for snapshot_entry in snapshots:
                if isinstance(snapshot_entry, list) and len(snapshot_entry) >= 2:
                    fiber_id = snapshot_entry[0]
                    fiber_data = snapshot_entry[1]
                    if isinstance(fiber_data, dict):
                        name = fiber_data.get('displayName', f'Fiber-{fiber_id}')
                        if name:  # Only add if we have a display name
                            fiber_map[int(fiber_id)] = name

    slow_fibers = []

    for root in data.get('dataForRoots', []):
        for commit in root.get('commitData', []):
            for fiber_id, duration in commit.get('fiberActualDurations', []):
                if duration >= SLOW_RENDER_THRESHOLD:
                    component_name = fiber_map.get(fiber_id, f'Component-{fiber_id}')
                    slow_fibers.append((component_name, duration, commit.get('timestamp', 0)))

    return slow_fibers

def print_critical_issues(results: Dict, slow_fibers: List):
    """Print only the critical issues that need attention"""

    print("\n" + "="*80)
    print("🔴 CRITICAL PERFORMANCE ISSUES - ONBOARDING FAVORITE STACK")
    print("="*80 + "\n")

    # Critical renders (>100ms)
    if results['critical_commits']:
        print(f"⚠️  CRITICAL: {len(results['critical_commits'])} renders took >100ms (blocking UI)")
        print("-" * 80)
        for i, commit in enumerate(results['critical_commits'][:10], 1):
            print(f"  {i}. Duration: {commit['duration']:.2f}ms")
            if commit['updaters']:
                for updater in commit['updaters']:
                    print(f"     Component: {updater.get('displayName', 'Unknown')}")
            print(f"     Affected components: {commit['components_affected']}")
            print()

        if len(results['critical_commits']) > 10:
            print(f"  ... and {len(results['critical_commits']) - 10} more critical renders\n")

    # Very slow renders (50-100ms)
    if results['very_slow_commits']:
        print(f"\n⚠️  WARNING: {len(results['very_slow_commits'])} renders took 50-100ms (noticeable lag)")
        print("-" * 80)
        for i, commit in enumerate(results['very_slow_commits'][:5], 1):
            print(f"  {i}. Duration: {commit['duration']:.2f}ms")
            if commit['updaters']:
                for updater in commit['updaters']:
                    print(f"     Component: {updater.get('displayName', 'Unknown')}")
            print()

        if len(results['very_slow_commits']) > 5:
            print(f"  ... and {len(results['very_slow_commits']) - 5} more slow renders\n")

    # Top problematic components
    print(f"\n📊 TOP 10 MOST EXPENSIVE COMPONENTS (by total time)")
    print("-" * 80)

    sorted_components = sorted(
        results['component_stats'].items(),
        key=lambda x: x[1]['total_duration'],
        reverse=True
    )[:10]

    for i, (name, stats) in enumerate(sorted_components, 1):
        avg_duration = stats['total_duration'] / stats['count'] if stats['count'] > 0 else 0
        print(f"  {i}. {name}")
        print(f"     Total time: {stats['total_duration']:.2f}ms across {stats['count']} updates")
        print(f"     Average: {avg_duration:.2f}ms | Max: {stats['max_duration']:.2f}ms")
        print()

    # Slowest individual component renders
    if slow_fibers:
        print(f"\n🐌 SLOWEST INDIVIDUAL COMPONENT RENDERS (>16ms)")
        print("-" * 80)
        sorted_fibers = sorted(slow_fibers, key=lambda x: x[1], reverse=True)[:15]

        for i, (component, duration, timestamp) in enumerate(sorted_fibers, 1):
            print(f"  {i}. {component}: {duration:.2f}ms")
        print()

    # Summary statistics
    print(f"\n📈 SUMMARY")
    print("-" * 80)
    print(f"  Total renders analyzed: {results['total_commits']}")
    print(f"  Critical (>100ms): {len(results['critical_commits'])} ({len(results['critical_commits'])/results['total_commits']*100:.1f}%)")
    print(f"  Very Slow (50-100ms): {len(results['very_slow_commits'])} ({len(results['very_slow_commits'])/results['total_commits']*100:.1f}%)")
    print(f"  Slow (16-50ms): {len(results['slow_commits'])} ({len(results['slow_commits'])/results['total_commits']*100:.1f}%)")
    print(f"  Good (<16ms): {results['total_commits'] - len(results['critical_commits']) - len(results['very_slow_commits']) - len(results['slow_commits'])}")
    print()

    # Recommendations
    print(f"\n💡 RECOMMENDATIONS")
    print("-" * 80)

    if results['critical_commits']:
        print("  1. URGENT: Focus on components causing >100ms renders first")
        print("     These are blocking the UI and causing visible stuttering\n")

    if len(results['very_slow_commits']) > 10:
        print("  2. Optimize components with 50-100ms renders")
        print("     These cause noticeable lag during user interactions\n")

    top_component = sorted_components[0] if sorted_components else None
    if top_component and top_component[1]['count'] > 10:
        print(f"  3. '{top_component[0]}' is updating frequently ({top_component[1]['count']} times)")
        print("     Consider: React.memo, useMemo, or useCallback to reduce re-renders\n")

    print("  4. General optimizations to consider:")
    print("     - Use FlatList/FlashList with proper key extraction")
    print("     - Memoize expensive computations with useMemo")
    print("     - Virtualize long lists")
    print("     - Lazy load images and heavy components")
    print("     - Use InteractionManager for non-urgent work")

    print("\n" + "="*80 + "\n")

def main():
    if len(sys.argv) < 2:
        print("Usage: python3 analyze_performance.py <profiling-data-file.json>")
        sys.exit(1)

    filepath = sys.argv[1]

    try:
        data = load_profile_data(filepath)

        print("🔍 Analyzing performance data...")
        results = analyze_commits(data)

        print("🔍 Analyzing component-level performance...")
        slow_fibers = analyze_fiber_durations(data)

        print_critical_issues(results, slow_fibers)

    except FileNotFoundError:
        print(f"❌ Error: Could not find file '{filepath}'")
        sys.exit(1)
    except json.JSONDecodeError as e:
        print(f"❌ Error: Invalid JSON in file - {e}")
        sys.exit(1)
    except Exception as e:
        print(f"❌ Error: {e}")
        import traceback
        traceback.print_exc()
        sys.exit(1)

if __name__ == '__main__':
    main()

메타데이터
post_id
5277ac5354a1
slug
how-i-evolved-my-react-native-performance-debugging-and-how-i-measure-performance-optimizations-5277ac5354a1
url
https://medium.com/@karimhekal/how-i-evolved-my-react-native-performance-debugging-and-how-i-measure-performance-optimizations-5277ac5354a1
canonical_url
https://medium.com/@karimhekal/how-i-evolved-my-react-native-performance-debugging-and-how-i-measure-performance-optimizations-5277ac5354a1
author_url
https://medium.com/@karimhekal
status
ok
fetched_at
2026-08-23 03:17:38