Readable React Native Crash Reports with Source Map Symbolication
Upload your source maps once and every crash stack resolves to your real file, line, and function — matched to incoming crashes automatically
August 21, 2026
Jump to section
TL;DR
- The problem: React Native crash stacks are minified bundle offsets —
index.android.bundle:1:428913— not your source. - The fix: run
npx vexo-upload-sourcemapsafter a release build to upload your source maps, keyed by app version, platform, and dist. - Automatic matching: incoming crashes already carry
app_versionandexpo_update_id, so Vexo finds the right map with no manual linking. - What you see: a Symbolicated badge on the stack trace, de-minified to your real file, line, and function.
When we added crash, ANR, and error monitoring to Vexo, we called out one thing it deliberately left for later: symbolication. You got the crash, the crash-free rate, the grouped issue, and the session it happened in — but the stack trace itself still looked like this:
TypeError: undefined is not an object
at index.android.bundle:1:428913
at index.android.bundle:1:71204
at index.android.bundle:1:99381That is what a production React Native bundle throws by default. Metro (and Hermes) minify your app into a single bundle, so every frame is a byte offset into generated code — accurate, but unreadable. It tells you something broke; it does not tell you where. This post is the follow-up: as of now, Vexo resolves those offsets back to your source.
What symbolication does
A source map is the lookup table your build produces alongside the minified bundle: it maps every position in the generated code back to the original file, line, column, and function name. Symbolication is the act of running a crash stack through that table. Given the frame index.android.bundle:1:428913 and the matching map, Vexo turns it into:
TypeError: undefined is not an object
at CheckoutScreen.onSubmit (src/screens/Checkout.tsx:84:19)
at handlePress (src/components/PayButton.tsx:31:7)
at commitHookEffectListMount (react-native/Libraries/Renderer/...)Same crash, now readable. Vexo handles both plain Metro/JavaScript maps and Hermes maps — the Hermes bytecode compiler emits its own composed source map, and Vexo resolves through it the same way — so the frames land in your real files whether or not you ship with the Hermes engine.
Upload your maps with one command
Symbolication only works if Vexo has the source map that matches the exact bundle your users are running. Maps are a build artifact you generate at release time and should not ship inside your app, so there is one step to add to your release flow: upload them. The SDK ships a CLI for exactly that. After you produce a release build, run:
# after a release build, from your project root npx vexo-upload-sourcemaps
The CLI locates the Metro/Hermes source maps for the build and uploads them to Vexo, keyed by app version, platform, and dist. That key is the whole trick: it is the same coordinate space your crashes report from, so a map you upload for 1.4.0 / android is the map Vexo reaches for when a 1.4.0 Android crash comes in. You upload once per release, per platform.
Automate it in CI. On Expo, the cleanest place to run the upload is an EAS post-publish hook, so every release that goes out also ships its maps to Vexo without anyone remembering to do it. The SDK README documents the Metro/Hermes map generation and the EAS hook wiring end-to-end.
Maps match crashes automatically
There is no step where you link a map to a crash. Every event Vexo already captures carries the app version and, on Expo, the expo_update_id for the exact JS bundle that was running. When a crash arrives, Vexo looks up the source map you uploaded under that same version and update and symbolicates the stack on ingest. Upload the map, and every crash from that build — the ones already in your dashboard and the ones still to come — resolves to your source. Miss the upload for a release, and those crashes simply stay minified until the map shows up.
You can see whether a stack was symbolicated
Open any error in Vexo and expand its Stack trace. Next to the heading is a small badge that tells you, at a glance, whether that frame set was de-minified:
- Symbolicated — the stack was de-minified against an uploaded source map. The frames point at your real files.
- Minified — no source map was uploaded for that release yet. Upload one with
vexo-upload-sourcemapsand the stack resolves.
The badge is per stack, not per project, because it reflects reality per release: a version you uploaded maps for reads Symbolicated, while a hotfix you shipped before wiring up the upload still reads Minified. It is also the fastest way to confirm your CI upload is actually working — ship a build, trigger a test error, and watch the badge flip to green.
Getting it
Crash and ANR capture already ship with vexo-analytics; symbolication adds one command to your release flow. Generate source maps as part of your release build (Metro and Hermes both emit them), then run npx vexo-upload-sourcemaps — by hand at first, then from an EAS post-publish hook or your CI release job so it happens on every build. See the integration docs for the full setup, or the crash & ANR reporting post for how crashes land next to your sessions and analytics in the first place.
Frequently Asked Questions
Why are my crash stacks minified in the first place?
Production React Native ships a single minified bundle from Metro (and Hermes bytecode on top of it). Every stack frame is a byte offset into that generated code, so without a source map there is no way to recover your original file and line. That is expected — the fix is to upload the map, not to disable minification.
Do I need to link maps to crashes manually?
No. You upload maps keyed by app version, platform, and dist, and incoming crashes already carry app_version and expo_update_id. Vexo matches them on ingest — there is no per-crash linking step.
Does it work with Hermes?
Yes. Hermes emits a composed source map for its bytecode, and Vexo resolves stacks through it the same way it handles a plain Metro/JavaScript map. Both engines are supported.
What happens to crashes from a release I forgot to upload maps for?
They stay minified and show the Minified badge. Uploading the matching source map later lets Vexo symbolicate that release; the badge reflects whether a map exists for each stack's specific version.
Where should I run the upload?
Anywhere your release build runs. Start by running npx vexo-upload-sourcemaps by hand after a build, then move it into an EAS post-publish hook or your CI release job so every release uploads its maps automatically. The SDK README covers the map generation and hook setup.
Questions or feedback? Reach out at hello@vexo.co or join our Discord.