Screen tracking
Screen load times are the one metric that needs a little wiring. perfbit reads them from React Navigation, so it needs a reference to your navigation container.
Heads up
Without navigationRef, screen tracking is silently disabled. Everything else — cold starts, frames, crashes, network — still works, but the Screens page will stay empty and no warning is printed unless you have debug: true set.
React Navigation
Create the ref with createNavigationContainerRef() at module scope, pass it to both PerfSDK.init() and your NavigationContainer:
import { PerfSDK } from '@perfbitapp/rn-sdk';
import {
NavigationContainer,
createNavigationContainerRef,
} from '@react-navigation/native';
// Created at module scope so it exists before init() runs
const navigationRef = createNavigationContainerRef();
PerfSDK.init({
apiKey: 'YOUR_API_KEY',
appVersion: '1.0.0',
navigationRef,
});
export default function App() {
return (
<NavigationContainer ref={navigationRef}>
<RootStack />
</NavigationContainer>
);
}The ref does not need to be populated when init() is called. The SDK polls for up to 2 seconds (20 attempts, 100ms apart) waiting for the container to mount, then attaches its listener. If the ref is still empty after that, it gives up.
Expo Router
Expo Router renders the navigation container for you, so use useNavigationContainerRef() and wrap the value in a ref-shaped object:
import { useEffect } from 'react';
import { PerfSDK } from '@perfbitapp/rn-sdk';
import { Stack, useNavigationContainerRef } from 'expo-router';
// Expo Router owns the container, so grab its ref and hand it over once.
export default function RootLayout() {
const navigationRef = useNavigationContainerRef();
useEffect(() => {
PerfSDK.init({
apiKey: 'YOUR_API_KEY',
appVersion: '1.0.0',
navigationRef: { current: navigationRef },
});
}, [navigationRef]);
return <Stack />;
}init() ignores repeat calls, so running it from an effect is safe even if the layout re-renders.
How the measurement works
The SDK subscribes to the container's stateevent. On each navigation it resolves the deepest active route name — walking into nested navigators, so a screen inside a tab inside a stack reports its own name, not the tab's.
It then records the time until InteractionManager.runAfterInteractions()fires, which is React Native's signal that the navigation animation and any queued interaction work have finished. That interval is load_duration_ms.
- Navigating to the screen you are already on emits nothing — the tracker de-duplicates on route name.
- The screen name comes from your navigator's route names, so keep them stable across releases or the Screens view will treat a rename as a new screen.
- The very first screen is measured too, as soon as the container is ready.
If you don't use React Navigation
There is currently no manual screen-tracking API — navigationRef is the only supported path, and it expects the React Navigation container interface (addListener('state') and getRootState()). Apps on a different router will get every other metric but no per-screen data.
Confirming it's working
Turn on debug and watch the console as you navigate:
PerfSDK.init({
apiKey: 'YOUR_API_KEY',
appVersion: '1.0.0',
navigationRef,
debug: true,
});
// Console output when it works:
// [perfbit] NavigationTracker: started
// [perfbit] NavigationTracker: screen_load HomeScreen 42msIf you see NavigationTracker: no navigationRef provided or navigationRef never became ready after 2s, the ref is not reaching the SDK — check that the same ref object is passed to both init() and NavigationContainer. More causes in Troubleshooting.