Filipe Sousa
arrow_backBack to Labs
Labs · Notes23 MAY 2026schedule8 min read

Migrating React Native from React Navigation to Expo Router

I planned this migration as four staged PRs. That was wrong. What actually shipped it was around twenty smaller PRs over four weeks — one stack at a time — because coexistence turned out to be the whole trick.

Why bother

React Navigation is fine. Expo Router is better for teams that ship a lot because file paths become routes. New engineers stop needing the "how does navigation work here" tour. Deep-linking aligns with the file tree without a separate config. Screen options move to _layout.tsx files at exactly the scope they apply.

And, most of the time the real trigger is: you want the same code to run on the web. React Navigation plus its web adapter is not a happy path there.

The shape of the work

The trick is: both libraries live in the app for most of the migration. React Navigation stays as the root. Expo Router sits inside it as a child, one stack at a time. Every PR moves one small subtree, ships to production the same week, gets a week of real user traffic before the next PR touches it.

Week one is foundations. Get the base app/ layout mounted inside the existing navigator. Serialize your route params (Expo Router requires URL-encodable params — this catches everything you were passing as objects). Move whatever global state lived in navigation.setParams somewhere else; I used Apollo reactive vars.

After that first PR the pattern is set. Every subsequent PR looks the same:

  • pick a stack that's small and low-traffic
  • copy the screens into their new home under app/
  • update callsites to route via expo-router
  • delete the old stack config
  • ship it

By the end of the first week the team could open one of these PRs without needing to be told what shape it should have. That was the point.

What moves in what order

Small and lightly-used first. Confidence builds. Bigger, higher-traffic areas last. The final stack to move is always the root navigator itself, because that's the load-bearing one — by then everyone has muscle memory.

Tabs are structurally different from stacks and worth their own PR. Expo Router uses (tabs) route groups; you'll spend an afternoon getting the layout composition right and then never think about it again.

Rollback

Because both libraries are present through the migration, every PR is git revert-able. If something breaks in a stack you just moved, revert the PR; the app falls back to the old code for that subtree; the rest keeps working.

The exception is the very last PR — the one that removes React Navigation entirely. That's the only irreversible step. By the time you get there, the new code has run in production for weeks. If it were going to break, it would have broken.

The follow-up that surprised me

A week after the migration completed I built a small primitive I hadn't planned. Overlays — toasts, floating menus, bottom sheets — that used to live in the navigation container had no obvious home under Expo Router. Every screen would render its own copy, clipped by the layout above it.

The fix is a context at the root that exposes overlay slots. Any child calls useOverlay().push(component) and it renders into the top-most layer. Replaced maybe a dozen ad-hoc portal-ish hacks and became the pattern for every modal after.

Not part of the migration. Emerged from it. I like when that happens — it means you left a better codebase behind, not just a different one.

Things I'd do differently

Two.

One: deep-link tests before the first PR. I retrofitted them mid-migration and while it worked, it added pressure I didn't need. Deep-links are exactly the shape of thing that regresses silently.

Two: a small "adapter" PR very early. A tiny compat helper that abstracts the underlying library so callsites don't import from @react-navigation directly. Would have kept per-stack PRs even smaller.

Signals it went well

The team started opening their own "move X to expo-router" PRs without being asked. PR sizes trended down as boilerplate disappeared. New engineers no longer needed the navigation orientation session. All measurable eight weeks later.

Closing

If you're staring at this migration and wondering whether to do it, the honest answer is: it depends how much you ship. If your team ships one or two features a month, the payoff is small. If you ship ten, Expo Router pays for itself inside a quarter.

#react-native#expo-router#migrations
Filipe Sousa · Senior Full-Stack Engineer