Every few months, a CTO reaches out with some version of the same question. Their team has a React Native app. It’s been running for two or three years. And now something’s changed: they need better performance, they want to ship on desktop, they’re adding hardware integrations that the JS bridge makes difficult. The question on the table is whether to migrate to Flutter.
This post is for that decision. Not “which framework should I pick for a new project” (that’s covered in our Flutter vs React Native comparison). This is the harder question: you already have a React Native codebase, your team knows it, and you’re weighing whether the cost of a migration is worth what you’d get on the other side.
We’ll be honest: Unlock’d has never done a full React Native to Flutter migration as a client engagement. What we have done is evaluated migration feasibility during discovery calls, audited React Native codebases to estimate what a Flutter rebuild would cost, and built Flutter apps in domains where React Native teams frequently hit ceilings. This post draws on that accumulated experience, not a single project.
Signs Your React Native App Is Hitting Limits
React Native works well for many apps. If yours falls into a category where it starts to strain, you’ll recognize these patterns.
Animation and rendering performance. React Native’s bridge architecture means UI updates pass through a JavaScript runtime. For most apps, this is invisible. But when you need frame-level control, such as 60fps animations, real-time visualizations, or UIs that update on every WebSocket message, the bridge becomes the bottleneck. Flutter renders directly, without a runtime in the loop. For the Lava auction platform, this mattered: the descending-price clock had to animate at 60fps even on low-end Windows hardware, with 100 price dots redrawing 20 times per second. That’s the kind of requirement where Flutter’s rendering model gives you tools React Native doesn’t expose.
Large list performance. React Native has improved here with the New Architecture, but teams still report inconsistent scroll performance on complex lists, especially with custom item renderers or nested views.
BLE and hardware integration. React Native’s BLE ecosystem is fragmented. You’re often stitching together multiple packages from different maintainers with different assumptions. Flutter’s BLE library is more consolidated: one well-maintained package covering the full connection lifecycle. If you’re adding hardware connectivity to your app, or if hardware integrations were the driver for looking at Flutter in the first place, this is a real consideration. The Classified Cycling companion app manages BLE connections to four device types in outdoor conditions. It was built in Flutter from the start, specifically for this reason.
Web and desktop expansion. React Native’s web and desktop support has improved, but it’s still a secondary target. If you need a single codebase to run reliably on iOS, Android, web, and Windows desktop, Flutter’s multi-platform support is genuinely equal across those targets. The Lava auction platform ships on Windows desktop and web from the same Flutter codebase, same rendering engine, same state management.
New Architecture migration friction. React Native’s New Architecture (Fabric + TurboModules) is a significant improvement, but migrating existing apps can be painful. If you’re facing a major React Native infrastructure upgrade anyway, some teams find the total cost of that migration comparable to the cost of a Flutter rewrite, at which point the question becomes what you’d prefer to end up with.
When Migration Does NOT Make Sense
This is the section you won’t often see in Flutter marketing material. Migration is not always the right call.
Your app is working fine. If users are happy, crashes are rare, and performance complaints are absent, the business case for a migration is weak. A migration is a large investment that produces a result that looks identical to users. The technical improvements need to translate into real business outcomes.
Your React Native team is productive and happy. The React Native ecosystem is mature. If your team is fast, confident, and not hitting walls, switching frameworks means a period of lower velocity while they learn Dart and Flutter patterns. That’s a real cost.
No multi-platform expansion planned. Flutter’s strength compounds when you ship to multiple platforms from one codebase. If mobile is your permanent target and you have no plans to add desktop or web, this advantage doesn’t apply to you.
Tight budget with no clear ROI. Migrations cost money. If you can’t articulate a specific business outcome from the migration (faster feature delivery, new platform support, reduced crash rates, hardware integration that’s currently impossible), the business case doesn’t hold. Cutting edge technology alone is not a business case.
Migration Cost and Timeline Ranges
These are honest estimates based on what we’ve seen during discovery engagements. They are not guarantees. Real projects are messier.
| App Complexity | Examples | Timeline | Relative Cost |
|---|---|---|---|
| Simple (5-15 screens, standard UI) | Content apps, dashboards | 2-3 months | 40-60% of original build |
| Medium (15-40 screens, custom UI, integrations) | E-commerce, booking apps | 4-6 months | 50-70% of original build |
| Complex (40+ screens, native modules, offline, hardware) | Fintech, IoT companion, healthcare | 6-12 months | 60-80% of original build |
A few things drive cost up faster than screen count suggests:
Native module equivalents. Every React Native native module needs a Flutter equivalent. Some have drop-in replacements. Others require custom platform channels or, in some cases, FFI bindings to native code. Inventory your native modules early. This is usually the biggest source of estimate variance.
State management rewrite. Redux and MobX patterns don’t map 1:1 to Riverpod or Bloc. The logic is portable, but the code is not. A large app with a mature Redux store will require a deliberate architectural redesign, not just a translation.
Platform-specific behavior differences. Edge cases in iOS and Android behavior that your team learned through production experience need to be re-learned in a Flutter context. Things like keyboard avoiding behavior, safe area handling, platform permission flows, and deep link routing all have slightly different idioms.
Testing infrastructure rebuild. Your existing widget tests and integration tests don’t transfer. You’re rebuilding the test suite from scratch in Flutter’s testing framework.
Common Migration Pitfalls
Teams that underestimate migrations almost always stumble on the same set of problems.
Migration Strategies
Not all migrations follow the same pattern. There are three main approaches, each with different risk profiles.
| Strategy | How It Works | Best When |
|---|---|---|
| Full rewrite | Build the Flutter app from scratch alongside the existing RN app; cut over when ready | App is small-to-medium, or the existing codebase has significant technical debt worth leaving behind |
| Strangler pattern | Embed Flutter modules inside the existing React Native app using flutter_boost or similar; replace screens incrementally | App is large and complex; teams need to maintain both versions in production during transition |
| Incremental screen replacement | Build new features in Flutter only; migrate high-traffic screens first; defer low-priority screens | Organization can tolerate running a hybrid codebase for an extended period |
For most teams we talk to, the full rewrite is the most practical choice for small-to-medium apps. The strangler pattern sounds appealing because it feels less risky, but running a hybrid RN + Flutter codebase is genuinely complex. You’re maintaining two state management systems, two navigation stacks, and a bridge between them. The operational overhead adds up quickly. The strangler pattern makes more sense for large, high-traffic apps where a full cutover is commercially risky.
If you’re considering a migration because of hardware integration needs, see our Flutter BLE app development post for what that work actually involves. If your app has e-commerce functionality and you’re weighing a rebuild versus a hybrid approach, our Flutter Magento integration post covers both patterns.
What a Discovery Engagement Looks Like
When teams come to us with migration questions, here’s how we approach the assessment.
Codebase audit. We review the existing React Native codebase: screen count, component complexity, native module inventory, state management architecture, test coverage, and CI/CD configuration. The goal is a factual inventory, not a judgment about quality.
Native module inventory. We map every native module to its Flutter equivalent, or flag it as needing custom work. This is the single most important input to the cost estimate. A codebase with five native modules that have well-maintained Flutter equivalents is very different from one with five modules that need bespoke implementations.
Complexity scoring. We assign a complexity tier to each major area: UI complexity, state management depth, native integrations, offline requirements, platform-specific behavior. This gives us a weighted estimate rather than a screen-count estimate.
Timeline and budget estimate. Based on the audit, we produce a range estimate with honest confidence intervals. We specify what assumptions drive the estimate and what would cause it to expand.
Go/no-go recommendation. We give our honest view on whether migration is worth it given the specific app and business context. If the answer is no, we say so. An accurate assessment is more useful than a project that starts for the wrong reasons.
Key Takeaways
Migration decisions deserve more rigor than most teams apply. Before committing, make sure you can answer these questions.
Can you name the specific problem migration solves? Performance on a particular screen, desktop expansion, a hardware integration you can’t currently build, an architecture that’s slowing your team down. If the answer is vague, the ROI will be too.
Have you inventoried your native modules? This single factor explains most of the variance in migration cost estimates. Do this before any other scoping work.
Is the business case for the migration time horizon acceptable? A 4-6 month migration that results in 18 months of faster feature delivery makes sense. A 6-month migration for an app that sees stable, low-frequency releases is harder to justify.
Have you considered the strangler alternative? For large apps, embedding Flutter modules incrementally may reduce commercial risk, even if it increases technical complexity. The right answer depends on your team size, commercial pressure, and risk tolerance.
The teams that have the best migrations are the ones that went in with accurate data. The teams that struggle are the ones that underestimated native module complexity and assumed the codebase would port faster than it did.
Considering a migration?
