Docs / TroubleshootingBrowse

Troubleshooting

Almost every 'perfbit isn't working' report is one of five things. Start with debug mode — it answers most of them in one run.

First: turn on debug mode

debug: true makes the SDK log everything it does, prefixed with [perfbit]. If you see the initialisation lines and periodic EventQueue: flushed N events, the SDK side is healthy and the problem is on the dashboard side.

PerfSDK.init({
  apiKey: 'YOUR_API_KEY',
  appVersion: '1.0.0',
  debug: true,
});

// Healthy startup looks roughly like:
// [perfbit] SessionTracker: started 550e8400-...
// [perfbit] StartupTracker: cold start 812ms
// [perfbit] NavigationTracker: started
// [perfbit] FrameTracker: started
// [perfbit] NetworkTracker: started
// [perfbit] PerfSDK: initialized { appVersion: '1.0.0', env: 'production', platform: 'ios' }
// [perfbit] EventQueue: flushed 6 events

No data in the dashboard at all

Note

Read the empty state first. The dashboard distinguishes four causes and names the one that applies — whether no event has ever arrived, events are arriving but tagged non-production, your data predates the selected window, or the active filters exclude it. It also shows a freshness chip in the sidebar with the time of the last event. That usually answers the question before you reach anything below. See Reading your dashboard.

1. Events were sent as non-production

This is the most common cause by a wide margin. Every dashboard query filters to environment = 'production'. Events tagged staging or development are validated, accepted and stored — the API returns a cheerful 200 — but nothing will ever render them.

Either send environment: 'production', or leave the option off entirely since production is the default.

2. The SDK is disabled in development

With environment: 'development' and no debug: true, init() returns immediately and no trackers start. You will see this in the console:

Note

[PerfSDK] Tracking disabled in development mode. Pass debug: true to enable.

3. Nothing has flushed yet

The queue flushes every 30 seconds, at 50 events, or on backgrounding. If you launched the app 5 seconds ago, nothing has been sent. Send the app to the background to force a flush, or call await PerfSDK.flush().

4. init() never ran

Check PerfSDK.isInitialized(). If init() is called inside a component that never mounts, or after an early return, no trackers start. Call it at module scope in your entry file.

5. The time range doesn't cover your data

The default range is 7 days. If you are looking for a build you shipped five weeks ago, widen the range — subject to your plan's retention. The dashboard widens automatically when you have not picked a range explicitly, and says so in a banner; if you have picked one, your choice is respected and nothing is widened for you.

Confirm the key works independently

This takes the app out of the equation entirely. A 200 here means the key is valid and ingestion is fine:

curl -X POST https://perfbit.app/api/ingest \
  -H "Content-Type: application/json" \
  -d '{
    "api_key": "YOUR_API_KEY",
    "app_version": "1.0.0",
    "environment": "production",
    "platform": "ios",
    "events": [{
      "type": "cold_start",
      "timestamp": 1718123456789,
      "session_id": "550e8400-e29b-41d4-a716-446655440000",
      "duration_ms": 500
    }]
  }'

# Expect: { "received": true, "count": 1 }

Ingestion errors

ErrorMeaningFix
invalid_api_key401 — no app matches this keyCopy it again from Settings → API Key. If someone regenerated the key, every build using the old one is now rejected.
invalid_payload400 — schema validation failedRead the details array. Most often session_id is not a valid UUID, a duration is zero where the schema requires a positive number, or platform is something other than ios/android/web.
invalid_payload with "malformed JSON"400 — body could not be parsedCheck the request body and Content-Type header.
rate_limited429 — over 100 requests in the 60s windowBack off for retry_afterseconds. The SDK's own batching stays well under this; hitting it usually means a custom integration sending one request per event.
internal_error500 — server-side failureRetry with backoff. If it persists, get in touch.

Screens page is empty but everything else works

Screen tracking needs navigationRef. Without it the tracker disables itself and — unless debug is on — says nothing.

With debug enabled you would see one of:

  • NavigationTracker: no navigationRef provided — the option was not passed to init().
  • navigationRef never became ready after 2s — the ref was passed but never attached to a NavigationContainer, or the container mounts more than two seconds after init() runs.

Full setup in Screen tracking.

Builds view has nothing to compare

Build comparison needs at least two distinct appVersion values in the retained window. If appVersion is hard-coded, every release collapses into one bucket. Read it from your app config so it moves with each build.

Numbers look wrong

A metric shows — instead of a number

The em dash means there were no samples to compute from, which is different from a measured zero. Crash-free rate on an app with no sessions shows — rather than 100%, and a Builds comparison that cannot be measured draws a dashed track rather than a flat bar. A real zero still renders as 0%.

Cold start seems too low

Cold start is measured from JavaScript module evaluation, not from process launch, so it excludes native startup and bundle load. It will read lower than a platform profiler. Importing the SDK earlier makes it more faithful. Details in Metrics.

The Network view has hundreds of near-identical rows

URLs keep their path parameters, so /users/123 and /users/456 are separate endpoints. There is no path templating today.

Crashes are missing

Only unhandled JavaScript errors are captured. Native crashes bypass the JS error handler entirely and are not counted. If your own error boundary or handler swallows an error before it reaches the global handler, perfbit will not see it either.

Request and response sizes are always 0

Payload size is not measured. The fields exist in the schema but are always sent as zero.

Events stop after a force-quit

The queue is held in memory and is not persisted. Whatever had not flushed when the process died is gone. Crash events are an exception — they trigger an immediate flush — but a user swiping the app away takes any pending batch with it.

Still stuck?

Email hello@perfbit.app with your app name and, if you have it, the [perfbit] debug output from a run.