Skip to content

fix(tracing): Support Expo Router 58 in expoRouterIntegration - #6923

Merged
alwx merged 4 commits into
mainfrom
alwx/bugfix/6901
Oct 27, 2026
Merged

alwx merged 4 commits into
mainfrom
alwx/bugfix/6901

Conversation

@alwx

@alwx alwx commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

📢 Type of change

  • Bugfix

📜 Description

expoRouterIntegration now gets the navigation container ref from the public useNavigationContainerRef export. It computes route info with getRouteInfoFromState, which Expo Router 57 and 58 both have at the same path. On Expo Router 58, the integration listens to the internal actionDispatched event and starts navigation spans through a new internal _startIdleNavigationSpan method on reactNavigationIntegration.

💡 Motivation and Context

Expo Router 58 removed expo-router/build/global-state/router-store and stopped sending __unsafe_action__. Metro then warned on every bundle, and the integration could not start navigation spans. Fixes #6901.

💚 How did you test it?

I ran expo export on samples/expo (expo-router 58.0.23). The warning appeared 10 times before this change and 0 times after it. I added unit tests for the new route info source, the action listener, and the listener cleanup.

📝 Checklist

  • I added tests to verify changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • No breaking changes.

🔮 Next steps

On Expo Router 58, actionDispatched fires after the new screen renders, so spans start a bit late. A follow-up can use the expo-router:action-enqueued performance mark for an earlier start time.

alwx added a commit that referenced this pull request Oct 21, 2026
Replace the placeholder in the CHANGELOG entry with the PR number, #6923.
@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Semver Impact of This PR

⚪ None (no version bump detected)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


  • fix(tracing): Support Expo Router 58 in expoRouterIntegration by alwx in #6923
  • fix(android): Persist hard-crash envelopes to disk before the process exits by alwx in #6937
  • feat(tracing): Track WebView trace-propagation feature adoption by antonis in #6938
  • build(deps): Bump body-parser to 2.4.0 to resolve DoS advisory by antonis in #6948
  • fix(android): Apply screenshot masking options to error and feedback screenshots by antonis in #6930
  • chore(deps): update Android SDK to v8.62.0 by github-actions in #6932
  • chore(deps): update Maestro to v2.12.0 by github-actions in #6945
  • chore(deps): bump getsentry/craft/.github/workflows/changelog-preview.yml from 2.35.0 to 3.0.0 by dependabot in #6944
  • chore(deps): bump the codeql-action group with 3 updates by dependabot in #6940
  • chore(deps): bump actions/upload-artifact from 7.0.1 to 7.0.2 by dependabot in #6941
  • chore(deps): bump fast-uri from 3.1.8 to 3.1.9 by dependabot in #6946
  • chore(deps): bump undici from 6.29.0 to 6.29.1 by dependabot in #6947
  • chore(deps): bump actions/setup-node from 7.0.0 to 7.1.0 by dependabot in #6943
  • chore(deps): bump ruby/setup-ruby from 1.327.0 to 1.334.0 by dependabot in #6942
  • chore(deps): bump ip-address from 10.7.2 to 10.7.4 by dependabot in #6939
  • chore(deps): update Wizard to v9.0.0 by github-actions in #6936
  • chore(deps): update CLI to v3.8.2 by github-actions in #6933
  • ci: Move CI off end-of-life Node versions to Node 22 by antonis in #6927

🤖 This preview updates automatically when you update the PR.

alwx added a commit that referenced this pull request Oct 22, 2026
Replace the placeholder in the CHANGELOG entry with the PR number, #6923.
@alwx
alwx marked this pull request as ready for review October 22, 2026 09:04
@alwx
alwx force-pushed the alwx/bugfix/6901 branch from 87f19fa to c899efa Compare October 22, 2026 09:04
@alwx
alwx force-pushed the alwx/bugfix/6901 branch from 87f19fa to c899efa Compare October 22, 2026 09:04
@alwx
alwx requested a review from a team as a code owner October 22, 2026 09:04

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/core/src/js/tracing/expoRouterStore.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit fc6a0b5. Configure here.

Comment thread packages/core/src/js/tracing/expoRouterIntegration.ts

@antonis antonis left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for tackling this! The Expo Router 58 path looks right: actionDispatched fires before the container's state event, and v57 has no internalNavigationEvents, so no navigation gets two spans.

Two blockers for older versions, details inline:

  1. Expo Router ≤55 silently loses the integration (getRouteInfoFromState is missing).
  2. v56/57 regress #6431 (navigationRef is read only once).

I verified both against the published expo-router packages (4.x through 58.x).

Comment thread packages/core/src/js/tracing/expoRouterStore.ts Outdated
Comment thread packages/core/src/js/tracing/expoRouterStore.ts Outdated
Comment thread packages/core/src/js/tracing/expoRouterStore.ts Outdated
Comment thread packages/core/test/tracing/expoRouterIntegration.test.ts
Comment thread CHANGELOG.md Outdated
alwx added a commit that referenced this pull request Oct 23, 2026
Replace the placeholder in the CHANGELOG entry with the PR number, #6923.
@alwx
alwx force-pushed the alwx/bugfix/6901 branch from 0e409bf to 1246c96 Compare October 23, 2026 12:00
alwx added a commit that referenced this pull request Oct 23, 2026
Review of #6923 found two regressions for Expo Router versions before 58.

Expo Router 55 and older do not ship getRouteInfoFromState, so the require
threw and the integration stopped as if expo-router were not installed. The
route info module is now optional. Without it, the integration still
registers the navigation container and skips the route override, so spans
use the React Navigation route names.

In Expo Router 56 and 57, useNavigationContainerRef reads a store that stays
empty until the Root Layout renders. The ref was read once at Sentry.init, so
polling timed out and brought back #6431. The store now reads the ref on every
access, and a throwing call returns undefined so it cannot escape the poll
timer.

The #6431 regression test is restored with the new mocks, and new tests cover
a missing getRouteInfoFromState and a throwing useNavigationContainerRef. The
CHANGELOG entry moves back to the Unreleased section after the 8.32.0 release.
@alwx
alwx requested a review from antonis October 23, 2026 12:14
@invivek26

Copy link
Copy Markdown

We backported this PR (at 1246c96) onto 8.28.0 to run expo-router 58.0.25, and real route changes now get navigation spans with the templated route name. Thanks for this.

One thing we hit on 58: actionDispatched fires for every reducer action, not only navigations. On a cold launch with tabs we saw ROUTE_NAMES_CHANGED twice and PRELOAD once per tab. router.prefetch() gives more PRELOADs and another ROUTE_NAMES_CHANGED, and param updates send SET_PARAMS.

expoRouterIntegration leaves useDispatchedActionData off, so startIdleNavigationSpan doesn't filter any of these. Each one starts an idle span, and the next state event keeps the same route key. The span then ships on its idle timeout as an unnamed Route Change navigation transaction with no route data. We got one on every cold launch and one per router.prefetch().

We worked around it by forwarding only navigation actions from the listener:

const NAVIGATION_ACTION_TYPES = new Set([
    'NAVIGATE',
    'NAVIGATE_TO_HREF',
    'PUSH',
    'REPLACE',
    'GO_BACK',
    'POP',
    'POP_TO',
    'POP_TO_TOP',
    'JUMP_TO',
    'RESET',
]);

// in the actionDispatched listener, after the `!event.actionType` check
if (!NAVIGATION_ACTION_TYPES.has(event.actionType)) {
    return;
}

With that, a cold launch ships one ui.load and nothing else. Tab switches (NAVIGATE), pushes, deep links and replace each ship one templated navigation transaction. GO_BACK still starts a span, and the empty-back rule drops it as before.

A denylist (PRELOAD, SET_PARAMS, ROUTE_NAMES_CHANGED) would also work for what we saw. But router 58's reducer has other internal actions too (REMOVE_ROUTES, NAVIGATOR_CHANGED, REPORT_CONSUMED), so an allowlist seemed safer.

@antonis antonis left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the changes Alex 🙇 Overall LGTM 👍
Let's also handle the report from @invivek26 #6923 (comment) 🙏

alwx added a commit that referenced this pull request Oct 27, 2026
Replace the placeholder in the CHANGELOG entry with the PR number, #6923.
alwx added a commit that referenced this pull request Oct 27, 2026
Review of #6923 found two regressions for Expo Router versions before 58.

Expo Router 55 and older do not ship getRouteInfoFromState, so the require
threw and the integration stopped as if expo-router were not installed. The
route info module is now optional. Without it, the integration still
registers the navigation container and skips the route override, so spans
use the React Navigation route names.

In Expo Router 56 and 57, useNavigationContainerRef reads a store that stays
empty until the Root Layout renders. The ref was read once at Sentry.init, so
polling timed out and brought back #6431. The store now reads the ref on every
access, and a throwing call returns undefined so it cannot escape the poll
timer.

The #6431 regression test is restored with the new mocks, and new tests cover
a missing getRouteInfoFromState and a throwing useNavigationContainerRef. The
CHANGELOG entry moves back to the Unreleased section after the 8.32.0 release.
@alwx
alwx force-pushed the alwx/bugfix/6901 branch from 1246c96 to 8f147a5 Compare October 27, 2026 11:00
@alwx
alwx requested review from a team as code owners October 27, 2026 11:00
@alwx
alwx requested a review from antonis October 27, 2026 11:11
@antonis antonis added the ready-to-merge Triggers the full CI test suite label Oct 27, 2026
@sentry

sentry Bot commented Oct 27, 2026

Copy link
Copy Markdown

📲 Install Builds

Android

🔗 App Name App ID Version Configuration
Sentry RN io.sentry.reactnative.sample 8.32.0 (112) Release

⚙️ sentry-react-native Build Distribution Settings

Comment thread CHANGELOG.md
### Fixes

- Apply `screenshot` masking options to Android error and feedback screenshots ([#6930](https://github.com/getsentry/sentry-react-native/pull/6930))
- Support Expo Router 58 in `expoRouterIntegration`. Metro no longer warns about the removed `expo-router/build/global-state/router-store` module, and the integration creates navigation spans again ([#6923](https://github.com/getsentry/sentry-react-native/pull/6923))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Expo Router ≤55 there's no getRouteInfoFromState, so the route override is skipped and transaction names change from the templated path to the React Navigation route name. Users with dashboards or alerts on those names will notice, so it's worth calling out:

Suggested change
- Support Expo Router 58 in `expoRouterIntegration`. Metro no longer warns about the removed `expo-router/build/global-state/router-store` module, and the integration creates navigation spans again ([#6923](https://github.com/getsentry/sentry-react-native/pull/6923))
- Support Expo Router 58 in `expoRouterIntegration`. Metro no longer warns about the removed `expo-router/build/global-state/router-store` module, and the integration creates navigation spans again ([#6923](https://github.com/getsentry/sentry-react-native/pull/6923))
- On Expo Router 55 and older (Expo SDK 52–55), navigation transactions now use React Navigation route names (for example `[id]`) instead of templated paths (`/profile/[id]`). To keep full paths, set `useFullPathsForNavigationRoutes: true`.

Could you check in the ≤55 test that useFullPathsForNavigationRoutes actually produces the old templated path? If it doesn't, drop that sentence. And if you'd rather restore templated names on ≤55 here (e.g. build segments from the focused route names and reuse buildExpoRouterTemplatedPath), the sub-bullet isn't needed.

@antonis antonis left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a comment on the changelog. Other than that LGTM 🎉

Expo Router 58 removed the internal router-store module and no longer emits
React Navigation's __unsafe_action__ event. Because 58 declares package
exports, Metro printed an invalid package.json warning for the router-store
require on every bundle, and the integration could not start navigation spans.

The navigation container ref now comes from the public
useNavigationContainerRef export, which returns a module-level ref in
Expo Router 57 and 58. Route info is computed from the root navigation state
with getRouteInfoFromState, which both versions ship at the same path.

On Expo Router 58 the integration listens to the internal actionDispatched
event and starts the idle navigation span through a new internal
_startIdleNavigationSpan method on reactNavigationIntegration. Expo Router 57
has no exports field, so the missing internalNavigationEvents path is a silent
optional dependency there and spans still start from __unsafe_action__.

Fixes #6901
alwx added 3 commits October 27, 2026 15:30
Replace the placeholder in the CHANGELOG entry with the PR number, #6923.
Review of #6923 found two regressions for Expo Router versions before 58.

Expo Router 55 and older do not ship getRouteInfoFromState, so the require
threw and the integration stopped as if expo-router were not installed. The
route info module is now optional. Without it, the integration still
registers the navigation container and skips the route override, so spans
use the React Navigation route names.

In Expo Router 56 and 57, useNavigationContainerRef reads a store that stays
empty until the Root Layout renders. The ref was read once at Sentry.init, so
polling timed out and brought back #6431. The store now reads the ref on every
access, and a throwing call returns undefined so it cannot escape the poll
timer.

The #6431 regression test is restored with the new mocks, and new tests cover
a missing getRouteInfoFromState and a throwing useNavigationContainerRef. The
CHANGELOG entry moves back to the Unreleased section after the 8.32.0 release.
Expo Router 58 emits actionDispatched for every reducer action, not only
user navigations. On a cold launch with tabs it sends ROUTE_NAMES_CHANGED
and one PRELOAD per tab, and router.prefetch() and param updates send more
PRELOAD, ROUTE_NAMES_CHANGED, and SET_PARAMS actions.

expoRouterIntegration does not enable useDispatchedActionData, so
startIdleNavigationSpan did not filter these. Each one started an idle span
that shipped as an empty "Route Change" transaction on the idle timeout.

The listener now forwards only the known navigation action types (NAVIGATE,
PUSH, REPLACE, GO_BACK, POP, POP_TO, POP_TO_TOP, JUMP_TO, RESET). An
allowlist is safer than a denylist because the router 58 reducer also has
internal actions such as REMOVE_ROUTES and NAVIGATOR_CHANGED.
@alwx
alwx force-pushed the alwx/bugfix/6901 branch from 8f147a5 to 2b5d588 Compare October 27, 2026 14:31
@alwx
alwx merged commit 2c53f06 into main Oct 27, 2026
42 of 76 checks passed
@alwx
alwx deleted the alwx/bugfix/6901 branch October 27, 2026 14:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge Triggers the full CI test suite

Projects

None yet

Development

Successfully merging this pull request may close these issues.

expo-router integration requires expo-router/build/global-state/router-store, but it was removed in SDK 58

3 participants