npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

vexo-analytics

v1.12.0

Published

The analytics tool for React Native.

Downloads

35,361

Readme

vexo-analytics

Vexo is the all-in-one, zero-config analytics and session-replay SDK for React Native. From a single line of code you get screen & tap analytics, session replay, heatmaps, crash & ANR reporting, and performance monitoring — no instrumentation required.

What you get

Once vexo() runs, the SDK captures the following automatically — no extra code:

  • Product analytics — screens, taps, and navigation, plus your own events via customEvent().
  • Session replay — native screen recording with privacy blur (opt out with sessionReplay: false).
  • Heatmaps — tap heatmaps, segmentable with setHeatmapSegment().
  • Crash & ANR reporting — uncaught JS errors are reported as fatal crashes, and a watchdog flags JS-thread stalls, feeding a crash-free rate and error grouping. Report handled exceptions with trackError().
  • Performance monitoring — app-start latency, slow/frozen frame counts, and per-screen render timing (opt out with performance: false).
  • Console capture — console.log / info / warn / error are recorded alongside the session.

Quickstart

Prerequisites

Expo

Getting started

  1. Create an account here.

  2. You'll be prompted into creating a new app, give it a cool name (you will be able to change that later) and once you submit it, you'll be given an API key.

  3. Run npx expo install vexo-analytics if you are using Expo, or yarn add vexo-analytics / npm install vexo-analytics otherwise. If you are using bare React Native, run pod install in the iOS folder.

  4. Add the following code to your app entry file (usually index.js, App.js or _layout.tsx if you're using Expo Router):

    import { vexo } from 'vexo-analytics';
    
    // You may want to wrap this with `if (!__DEV__) { ... }` to only run Vexo in production.
    vexo('YOUR_API_KEY');
  5. Re-build and run your app (the vexo-analytics package includes native code).

  6. Go to your app's page on Vexo and you should see your first event!

Wait, that's it? Yes! That's it. With that ease of integration you get an incredible set of features — go check them out!

API

Everything below is a named export of vexo-analytics.

| Export | Signature | What it does | |---|---|---| | vexo | vexo(apiKey: string, options?: VexoOptions): void | Initialize the SDK. VexoOptions = { sessionReplay?: boolean; performance?: boolean }. | | customEvent | customEvent(name: string, args: object): void | Send a custom analytics event with arbitrary properties. | | trackError | trackError(error: unknown, options?: { handled?: boolean }): void | Report a caught exception. Pass { handled: false } to count it against the crash-free rate. | | identifyDevice | identifyDevice(id: string \| null): Promise<void> | Attach a stable identifier (e.g. an email) to the device; pass null to un-identify. See the PII note below. | | setHeatmapSegment | setHeatmapSegment(value: string \| null \| undefined): void | Tag subsequent taps and heatmap screenshots with a segment label (1–64 chars). Pass null/undefined/'' to clear it. | | trackScreen | trackScreen(name: string, properties?: object): void | Manually record a screen change. Use it for navigators auto-attach doesn't cover — react-native-navigation (Wix) and custom navigation. See Navigation integration. | | enableTracking / disableTracking | (): Promise<void> | Turn all tracking and recording on/off at runtime; the choice persists across launches. | | VexoMask / VexoUnmask | React components | Wrap a subtree to redact it from session replay on-device (VexoUnmask opts a child back out). See session replay masking. | | VexoProvider | React component | Explicit navigation-wrap escape hatch — see Navigation integration. |

import { customEvent, trackError } from 'vexo-analytics';

// Custom event with properties
customEvent('checkout_completed', { total: 42.0, currency: 'USD', items: 3 });

// Report a caught exception (does not crash the app)
try {
  await risky();
} catch (err) {
  trackError(err);                     // handled by default
  // trackError(err, { handled: false }); // counts against the crash-free rate
}

Navigation integration & supported versions

vexo() auto-attaches to your navigation library by wrapping its container's render. All navigation integrations are optional peer dependencies — installing vexo-analytics in an app without @react-navigation/native produces no peer warnings.

| Integration | Supported | Notes | |---|---|---| | @react-navigation/native | 5 – 7 | Optional peer. Example app runs v7. | | Expo Router | v1 – SDK 57 (latest) | Zero-config auto-attach patches the NavigationContainer forwardRef Expo Router renders from a private internal (expo-router/build/fork/NavigationContainer; src/fork/... for the v1/v2 legacy path). Expo Router has no public API to subscribe to route changes without a component in your tree, so this is the only zero-config hook — a CI lane (scripts/verify-expo-router.js) asserts it still resolves and is patchable on Expo SDK 53 and the latest SDK, so a bump that moves it fails our build, not your screen tracking. If auto-attach ever can't hook it, it logs one dev-mode warning; use the public path below. | | react-native-navigation (Wix) / custom nav | manual | Not auto-attached. Report screens yourself with trackScreen(). | | react-native | * (tested: 0.74 – latest) | New Architecture supported (TurboModule); the old architecture keeps working via interop. Every release is compiled against a floor + latest RN matrix in CI (iOS + Android). | | react | * (tested: 18) | |

Explicit wrap (escape hatch): if auto-attach warns that it could not patch your navigation library (frozen module exports, an unsupported version, or no library at all), wrap the container yourself — this uses only public react-navigation APIs and no patching:

import { VexoProvider } from 'vexo-analytics';
import { NavigationContainer } from '@react-navigation/native';

export default function App() {
  return (
    <VexoProvider apiKey="YOUR_API_KEY" container={NavigationContainer}>
      <RootNavigator />
    </VexoProvider>
  );
}

When auto-attach cannot patch a library it found, it logs a dev-mode warning and cleanly no-ops instead of crashing; with no navigation library at all, vexo() logs an error at render time.

Expo Router — public path (no private internals): auto-attach's Expo Router hook relies on a private internal (see the table above); it's CI-verified on SDK 53 and the latest SDK, but if you'd rather not depend on it, subscribe with Expo Router's public useNavigationContainerRef() in your root app/_layout.tsx and forward each route to trackScreen():

import { useEffect } from 'react';
import { Slot, useNavigationContainerRef } from 'expo-router';
import { vexo, trackScreen } from 'vexo-analytics';

vexo('YOUR_API_KEY');

export default function RootLayout() {
  const navRef = useNavigationContainerRef();
  useEffect(() => {
    if (!navRef) return;
    const report = () => {
      const name = navRef.getCurrentRoute()?.name;
      if (name) trackScreen(name);
    };
    report(); // initial screen
    return navRef.addListener('state', report);
  }, [navRef]);

  return <Slot />;
}

trackScreen() de-dupes a repeat of the current route, so this composes safely even while auto-attach is active — but drive one or the other per navigator.

Manual screen tracking (trackScreen)

For navigators auto-attach can't wrap — react-native-navigation (Wix), a custom/in-house navigator, modals, wizard steps, or WebView routes — call trackScreen() yourself on each screen change. It emits the same screen event and drives the same route pipeline (heatmaps, per-screen render timing, crash route) as the auto-attach:

import { trackScreen } from 'vexo-analytics';

// e.g. from react-native-navigation's componentDidAppear / a listener:
trackScreen('Home');
trackScreen('Checkout', { step: 2 }); // optional extra properties

Use trackScreen() or auto-attach for a given navigator, not both — they report the same screens and would double-count. As a safeguard, trackScreen() treats a repeat of the current route as a no-op (the same dedupe auto-attach applies).

User consent & session replay

Vexo records user sessions (screens, taps, navigation). Under App Review Guideline 2.5.14, apps that record user activity must obtain explicit user consent and provide a clear visual indication that recording is taking place. Similar obligations apply under GDPR/CCPA. You, the app developer, are responsible for the consent flow; the SDK gives you the switches:

  • Consent-gated initialization (recommended): don't call vexo(apiKey) until the user has consented. Nothing is recorded or sent before vexo() runs.

    import { vexo, enableTracking, disableTracking } from 'vexo-analytics';
    
    if (userHasConsented) {
      vexo('YOUR_API_KEY');
    }
  • Opt-out at runtime: disableTracking() stops event collection and session recording (it also stops an in-flight recording); the choice is persisted across launches. enableTracking() turns it back on (recording resumes with the next session).

  • Visual indication: the SDK does not render a recording indicator; show your own persistent indicator while tracking is enabled to satisfy 2.5.14.

Disabling session replay (or performance capture) in code

To keep session replay off entirely while all other tracking (events, screens, taps, crashes) keeps working, pass sessionReplay: false when initializing. Independently, pass performance: false to disable performance capture:

import { vexo } from 'vexo-analytics';

vexo('YOUR_API_KEY', { sessionReplay: false });          // no screen recording / blur
vexo('YOUR_API_KEY', { performance: false });            // no perf capture
vexo('YOUR_API_KEY', { sessionReplay: false, performance: false });

Both decisions are made client-side and synchronously — no network round-trip, and no native recorder (or privacy blur) work is ever started, so sessionReplay: false is also the switch to reach for in performance-sensitive apps where recording on the New Architecture causes main-thread jank. Server-side settings can further restrict recording but can never re-enable it once the app passed false. Neither option is persisted: each vexo() call decides for that session, and omitting it (or passing true) keeps the default behavior.

Masking: independent of the whole-frame isBlurred blur, you can redact individual views. Masked rects are replaced with a solid block natively, on-device, before the frame is encoded or uploaded.

  • Per-view: wrap a sensitive subtree in <VexoMask>; use <VexoUnmask> to opt a child back out.

    import { VexoMask, VexoUnmask } from 'vexo-analytics';
    
    <VexoMask>
      <CreditCardForm />
      <VexoUnmask><Text>Last 4: 1234</Text></VexoUnmask>
    </VexoMask>
  • Defaults: pass masking to vexo(). maskAllTextInputs defaults to on; the others default off. Password / secure text inputs are always masked regardless.

    vexo('YOUR_API_KEY', {
      masking: { maskAllText: true, maskAllTextInputs: true, maskAllImages: false },
    });

Masking applies to the recorder only; it does not affect the whole-frame blur, which you can still keep on for screens you can't verify.

PII: identifyDevice(email) attaches the email to the device record on Vexo's servers. Only call it with the user's consent, and route deletion requests to Vexo support.

Store compliance notes

  • iOS Privacy Manifest: the pod ships a PrivacyInfo.xcprivacy declaring its required-reason API usage (NSUserDefaults CA92.1, file timestamps C617.1) and collected data types (product interaction, device ID, user ID, crash/performance/diagnostic data; no tracking). Include Vexo's data collection in your App Store privacy nutrition label.
  • Android 16 KB page sizes: the SDK's native dependencies are 16 KB-aligned (Google Play requirement for updates from 2027-02-01), enforced by a Gradle version floor on co.vexo:renderscript-intrinsics-replacement-toolkit (>= 0.8.1) plus a CI alignment check. If you build with AGP < 8.5.1, check your APK/AAB packaging (useLegacyPackaging) so aligned libraries stay uncompressed and page-aligned.

Source maps & symbolication

In release builds the JS bundle is minified/Hermes-compiled, so crash stacks arrive as unreadable frames like at t (index.android.bundle:1:452103). Upload the composed source map for each release and Vexo de-minifies those frames in the crash/error UI.

1. Generate the composed source map when you build the release bundle:

# React Native CLI (Hermes)
npx react-native bundle \
  --platform android --dev false --minify true \
  --entry-file index.js \
  --bundle-output index.android.bundle \
  --sourcemap-output index.android.bundle.map

Metro 0.87+ makes source-map generation ~2x faster / half the memory, so this is cheap. For Hermes, use the map composed with the bytecode map (compose-source-maps), which react-native bundle produces for you.

2. Upload it with the bundled CLI (uses your app's API key):

npx vexo-upload-sourcemaps \
  --platform android \
  --sourcemap index.android.bundle.map \
  --api-key "$VEXO_API_KEY"        # or set VEXO_API_KEY
  # --app-version 1.2.3            # auto-detected from app.json/package.json
  # --dist <eas-update-id>         # for EAS Update OTA builds (default: embedded)

The map is keyed by (appVersion, platform, dist) — the same identifiers the SDK stamps on every event (app version + the Expo update id) — so incoming crashes match the right map automatically. No SDK code change is needed to opt in; just upload the map for each release.

Expo / EAS Update: run the upload after eas update, passing the update's id as --dist, so OTA releases get their own map:

eas update --branch production --message "..."
npx vexo-upload-sourcemaps --platform android \
  --sourcemap dist/_expo/static/js/android/index-*.map \
  --dist "$(eas update:view --json | jq -r '.id')"

Native crash symbolication (dSYMs / Android mapping.txt) is a follow-up; JS/Hermes maps are supported today.

Development

  • yarn test — unit tests (Jest, React Native preset).
  • maestro test .maestro/smoke.yml — E2E smoke flow (Maestro); build and install the example app on a running emulator/simulator first. Not wired into CI yet (needs an emulator job).
  • CI runs on GitHub Actions (.github/workflows/ci.yml), plus a native-compile matrix (native-build.yml) that builds the module in a real app across a React Native version range on iOS and Android. Releases and native SDK builds are documented in CONTRIBUTING.md; changes are tracked in CHANGELOG.md.