React Native

React Native analytics and crash reporting

One npm install, one provider, and the instrumentation writes itself.

Most React Native analytics work is not analysis. It is instrumentation: adding a tracking call to every button, remembering to fire a screen view on every route, keeping event names consistent across a team, and discovering six months later that the screen you most wanted to measure was never wired up.

Nohmo removes that work with a Babel plugin that runs at compile time, wrapping every onPress and onLongPress and instrumenting NavigationContainer — so taps and screen views arrive with no call sites in your code. On top of it sit the things a mobile team actually asks for: funnels, retention, install attribution, and JavaScript and native crash reporting in the same session model.

This page covers all of it — the analytics SDK, crash reporting, what works under Expo, and where we stand on session replay, which is a section that ends by recommending somebody else.

React Native analytics SDK

The Babel plugin is the part that makes this different from every SDK that asks you to instrument by hand. Added once to babel.config.js, it wraps onPress and onLongPress props at compile time and injects navigation listeners onto NavigationContainer. There is no runtime wrapping cost and nothing to remember at each call site.

Everything else follows from a single provider at the root of the app. Installs, opens, backgrounds, screens, taps, rage taps, crashes and install attribution are all captured from there, and the same npm package covers your React, Next.js or plain HTML surfaces into the same dashboard and the same identity model.

Tap autocapture with no call sites

Every PRESS carries the component name, the static label if there is one, and the file and line it came from — so you can find the button in your editor straight from the report.

Screen views from your navigator

The plugin instruments NavigationContainer directly, so every navigation state change becomes a SCREEN_VIEW with time spent. Prefer explicit control? useScreenView("Home") does the same job per screen.

Install attribution built in

Android reads the Play Store referrer, iOS checks the system pasteboard for a click token written when the ad was tapped. No MMP and no extra package — campaign parameters land on the install itself.

Identity that works backwards

Call linkUser() after login and every event that device ever fired, including sessions before the account existed, is attached to that user on the backend rather than orphaned.

Funnels, journeys and retention

Build a funnel in the dashboard and read it immediately. Session journeys show each step in order with time spent, rather than only an aggregate you have to interpret.

Batched, persisted, non-blocking

Events are queued in memory, persisted to disk and flushed as a batch on an interval. Nothing blocks the JS thread, and queued events survive the app being killed mid-session.

Conversions with a value

trackConversion() against a goal defined in Settings → Conversions, optionally with an amount and currency — which is what later lets errors be ranked by revenue lost rather than by frequency.

The same SDK on web

One npm package covers React, Next.js, plain HTML and React Native. One dashboard, one identity model, one bill — instead of reconciling two vendors that disagree about what a user is.

React Native crash reporting

React Native crashes in two places and most tooling only watches one. A JavaScript exception is caught by a global handler and reported normally. A native crash — an uncaught Kotlin exception, a Swift force-unwrap on nil, a signal on iOS — kills the process before any JavaScript runs, so the report has to survive the death of the app that was writing it.

Nohmo installs handlers on both sides, and the native ones ship inside the SDK with no extra packages to add. A native crash is written to disk as it happens and reported as APP_CRASH on the next launch, attributed back to the session it actually happened in rather than the one that reported it.

What changes how you triage is what comes attached. Because a crash is just another event in the same session model, every one carries the journey that produced it — the screens, taps and funnel step immediately before the app died.

Native crashes on both platforms

Android catches uncaught Java and Kotlin exceptions on any thread. iOS catches Objective-C exceptions plus Swift fatalError, force-unwraps and memory crashes through signal handlers.

JavaScript errors, on by default

Uncaught exceptions and unhandled promise rejections are captured as JS_ERROR with no setup at all. autoErrors: false turns the whole thing off in one prop if you would rather wire it yourself.

The journey to the crash

Each occurrence opens onto the session timeline up to the moment it broke. This is usually the difference between a bug you can reproduce and one you close as unreproducible.

Grouped by signature

Errors are grouped with occurrence counts, affected users and devices, and a sample stack trace — then drill into every individual occurrence rather than an aggregate.

Crash-free rate per release

Each app version compared against the build it replaced, so a regression reads as a comparison rather than something you infer from a rising count.

The failures that never throw

A Pressable wired to a handler that returns early. A form that submits into the void. Nothing raises, so no crash reporter sees it — while the user taps three times and leaves. Nohmo detects these behaviourally.

How Nohmo detects them

Alerts the moment it happens

An Event Match webhook on JS_ERROR or APP_CRASH under Settings → Webhooks fires in real time, so a bad release reaches you before it reaches your reviews.

Bounded by default

Messages are truncated, query strings are stripped from request URLs, and Nohmo never reports failures of its own tracking endpoint.

Stack traces are raw and unsymbolicated today — dSYM and ProGuard mapping are planned but not shipped. If readable native traces are what you need right now, Crashlytics and Sentry both do this and we do not.

Expo analytics

Start with the constraint, because most vendors bury it and you will find out at the worst moment. Nohmo ships native modules — crash handlers on both platforms, the Play Store install referrer on Android, pasteboard attribution on iOS. Expo Go bundles a fixed set of native modules and cannot load arbitrary ones, so the SDK will not run inside Expo Go. No configuration fixes this; it is what Expo Go is.

It does work with Expo development builds and EAS Build, which is where most Expo apps past the prototype stage already live. The package ships an autolinking config, so once you have run a prebuild or created a dev client the native side is picked up without editing an Xcode project or a Gradle file. Expo Router is supported too — it is built on React Navigation, which the Babel plugin already instruments.

Autolinked into your dev build

The package ships a react-native.config.js, so Expo autolinking picks up the iOS and Android modules during prebuild. Nothing to edit by hand.

Composes with babel-preset-expo

Add the plugin alongside Expo’s preset in babel.config.js, then run expo start --clear. The Metro cache is the usual reason a first attempt captures nothing.

Expo Router out of the box

Expo Router sits on React Navigation, so screen views are captured with no extra configuration. Mount the provider in app/_layout.tsx wrapping Slot.

If Expo Go is non-negotiable for your team, this is not your tool — and a JS-only analytics SDK is the honest answer, accepting that you lose native crash capture and install attribution with it, because both need native code.

React Native session replay

Nohmo does not have session replay, it is not on our roadmap, and if replay is the reason you are here then one of the tools below is what you want. We would rather say that plainly than waste your evaluation.

What Nohmo keeps instead is the journey: every screen, tap, rage tap and funnel step in order, with time spent, attached to the session and to any error that occurred in it. That answers "what did this user do before it broke" without recording anything — no frame capture cost, no sampling, and a far smaller privacy surface. It will never show you what the screen looked like, so for a visual bug, a broken layout or an off-screen control it is not a substitute and pretending otherwise would not help you.

Replay on React Native is also genuinely harder than on the web. There is no DOM to serialise, so tools snapshot the native view hierarchy or capture frames, which costs more in CPU, payload and battery — which is why mobile replay is usually sampled, and why the session you most want to watch may not have been recorded.

LogRocket

compare

The most replay-centric product, with network calls and state alongside the recording. Start here if replay is the primary workflow.

Sentry

compare

Replay attached to an error you were already monitoring, with symbolicated traces. Replays meter separately from errors.

PostHog

Replay bundled with analytics, feature flags and experiments on a generous free tier. Mobile replay is newer than the web product.

UXCam

Mobile-only and built for UX research — recordings with heatmaps, gesture analysis and well-considered privacy masking.

Instabug

Recording paired with in-app bug reporting and shake-to-report, so a report arrives with what the user said and what they did.

Embrace

Complete session telemetry rather than video — every event, network call and ANR. Often more useful than a recording, and cheaper to store.

If you adopt replay, treat masking as a first-class task. A recording of a real user is a far larger privacy surface than an event stream, and the defaults are rarely sufficient for a screen showing personal data.

Captured without writing code

Captured once the provider is mounted and the Babel plugin is added — no per-component code.

EventTrigger
APP_INSTALLFirst ever app open — platform, appVersion, osVersion
APP_OPEN / APP_BACKGROUNDEvery foreground and background, with session duration and screen
INSTALL_ATTRIBUTEDPlay Store referrer on Android, pasteboard click token on iOS
SCREEN_VIEW / TIME_SPENTEvery NavigationContainer state change, Expo Router included
PRESS / LONG_PRESSAny onPress or onLongPress — with component, text, file and line
RAGE_CLICKThree taps on the same control inside a second
JS_ERRORUncaught exceptions and unhandled promise rejections
APP_CRASHFatal JS crashes and native crashes — reported on next launch

Setting it up

  1. 1

    Install

    The core package plus AsyncStorage, which is what makes a device recognisable across restarts. Native crash handlers and install attribution are inside the SDK — there is nothing else to add.

    bash
    npm install nohmo
    npm install @react-native-async-storage/async-storage
    cd ios && pod install
  2. 2

    Add one line to babel.config.js

    The step that removes the instrumentation work. On Expo, swap the preset for babel-preset-expo and run expo start --clear afterwards — modules cached before the config changed are not re-transformed.

    js
    module.exports = {
    presets: ['module:@react-native/babel-preset'], // 'babel-preset-expo' on Expo
    plugins: ['nohmo/babel-plugin'], // ← taps + screen views, no code changes
    }
  3. 3

    Mount the provider

    The whole setup. On Expo Router this goes in app/_layout.tsx wrapping Slot; otherwise App.tsx. Pass appVersion so crashes can be attributed to a release and compared against the previous one.

    tsx
    // App.tsx
    import { NohmoProvider } from 'nohmo/react-native'
    import AsyncStorage from '@react-native-async-storage/async-storage'
    export default function App() {
    return (
    <NohmoProvider
    projectId="proj_xxxx" // Settings → General → SDK credentials
    apiKey="pk_xxxx"
    options={{ appVersion: '1.0.0', debug: __DEV__, storage: AsyncStorage }}
    >
    <YourApp />
    </NohmoProvider>
    )
    }
  4. 4

    Add your own business events

    Autocapture covers interaction; these are the events worth naming by hand. Events are unlimited on a flat plan, so there is no reason to keep the payloads thin.

    tsx
    import { useNohmo } from 'nohmo/react-native'
    const { send, linkUser, trackConversion } = useNohmo()
    send('CHECKOUT_STARTED', { cartValue: 49.99, items: 3 })
    // Retroactively attaches all prior anonymous activity to the user
    await linkUser(user.id, user.email, { plan: user.plan })
    trackConversion('purchase', { amount: 29.99, currency: 'USD' })
  5. 5

    Tell Nohmo about each release

    Point CI at the release endpoint so crash-free rate is compared build to build. Idempotent on version and platform, so re-running a pipeline is safe.

    bash
    curl -X POST https://www.nohmo.in/api/tracker/release/ \
    -H "X-API-Key: $NOHMO_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"version":"2.4.1","platform":"ios","commitSha":"'"$GIT_SHA"'"}'

What this does not do

The gaps, named specifically, so you find them here rather than three weeks into an integration.

  • Stack traces are raw and unsymbolicated. dSYM and ProGuard mapping are planned but not shipped, so native frames arrive as addresses rather than method names. Crashlytics and Sentry both do this today and we do not.
  • Android NDK and C++ crashes are not captured, and neither are ANRs or out-of-memory terminations. If your crashes are in native modules, Crashlytics or Embrace is the better tool.
  • No session replay, and it is not on the roadmap. The journey is reconstructed from events, which is a different thing from a recording.
  • Expo Go is not supported and cannot be, because the SDK ships native modules. Development builds and EAS are fine.
  • Tap and screen autocapture depend on the Babel plugin. Without it you still get sessions, errors and any events you send by hand, but screens need useScreenView() per screen.
  • No A/B testing, feature flags or remote config, and analytical depth is deliberately narrower than Amplitude or Mixpanel — no predictive cohorts or advanced behavioural modelling.

Frequently asked questions

What is the best analytics SDK for React Native?+

It depends what you are optimising for. For behavioural depth with an analyst to use it, Amplitude. For experimentation, Statsig or PostHog. For free at any volume, Firebase. Nohmo suits teams who want tap and screen autocapture with no instrumentation work, install attribution and crash monitoring in the same package, on a flat bill.

Do I have to add tracking code to every button?+

No. Add plugins: ["nohmo/babel-plugin"] to babel.config.js and every onPress and onLongPress is wrapped at compile time, with screen views taken from NavigationContainer. You only write code for your own business events.

Does Nohmo catch native crashes or only JavaScript errors?+

Both. On Android it catches uncaught Java and Kotlin exceptions on any thread; on iOS, Objective-C exceptions plus Swift fatalError, force-unwraps and memory crashes via signal handlers. Because a dying process cannot make a network call, the crash is written to disk and reported as APP_CRASH on next launch, attributed back to the session it happened in.

Do I need extra packages for native crash reporting?+

No. The native handlers ship inside the nohmo package for both platforms — nothing to add beyond pod install on iOS. AsyncStorage is recommended so the device ID persists across restarts, and Firebase messaging is only needed if you also want uninstall detection.

Does Nohmo work with Expo?+

With Expo development builds and EAS Build, yes — including Expo Router. Not with Expo Go, because the SDK ships native modules for crash capture and install attribution, and Expo Go can only load native modules compiled into it. Install expo-dev-client, run expo prebuild, and build a development profile with EAS.

My taps are not being captured under Expo — what did I miss?+

Usually the Metro cache. The Babel plugin transforms your source at compile time, so modules already cached before you edited babel.config.js are not re-transformed. Run npx expo start --clear, and confirm the plugin is listed in the config Expo actually loads.

Does Nohmo have session replay for React Native?+

No, and it is not on the roadmap. Nohmo reconstructs the journey from events — screens, taps and funnel position in order — which is not the same as a recording. If replay is what you need, LogRocket, Sentry, PostHog, UXCam, Instabug or Embrace are the tools to evaluate.

Why are the stack traces not symbolicated?+

Because we have not built it yet. Sentry and Crashlytics upload dSYM and ProGuard mapping files at build time and resolve frames against them; we show raw traces today. It is a genuine gap and the most common reason a team keeps a second reporter installed alongside.

Can I run this alongside Crashlytics or Sentry?+

Yes, and during an evaluation you should. Crash handlers chain rather than replace one another, so an existing reporter keeps receiving everything it did before. Ship both through one release and compare what each caught on real devices.

Is there a limit on event volume or event names?+

No cap on distinct event names, and events are unlimited on the flat plan — $49/month per project during early access, normally $79. Instrumenting more thoroughly never increases the bill, which is the opposite of how event-metered tools behave.

Will tracking slow the app down?+

Events are queued in memory, persisted to disk and flushed as a batch on an interval rather than sent individually. Nothing blocks the JS thread, and queued events survive the app being killed mid-session.

The verdict

If you need symbolicated native traces, ANR detection, NDK coverage or session replay, this is not the whole answer and Crashlytics, Sentry or LogRocket will serve you better on each — we would rather point you there than argue. If the actual problem is that instrumenting a React Native app is tedious and never quite finished, and that your analytics, attribution and crash data live in three products that disagree about what a session is, one Babel line and one provider replaces all of it for a flat fee that a bad release cannot move.

Ship it this afternoon

One package, one provider, and React Native analytics and crashes are reporting into the same dashboard as the rest of your stack.

No credit card required · $49/mo during early access (normally $79)

Feature parity across React Native, Flutter and web at the same SDK version.