Rewriting a mobile app is easy to start and hard to finish. Rewriting one that hundreds of thousands of people rely on, while keeping the current version healthy, is a different discipline altogether. We maintained Rocket Lawyer's legal advice app, built with Ionic and used worldwide, while developing its new React Native successor. This article is not a case study with numbers. It is the set of practical lessons that matter in any migration of this kind.
We have also built hybrid apps for a long time: the Ionic and Angular mobile app for Asora, and a hybrid app with SIP.js and WebRTC calling for CloudTalk. So this is not a "hybrid is bad" story. Ionic is a good technology. Migration is a business decision that has to justify its cost.
Decide why you are migrating
Before any code, write down the reasons. Common legitimate ones:
- Interactions that need to feel fully native: gestures, lists with thousands of items, complex animations.
- Deeper integration with platform APIs, background tasks or native SDKs.
- Performance on lower-end Android devices.
- A company-wide move to React and TypeScript, making hiring and code sharing easier.
Weak reasons include "React Native is more modern" or "the old code is messy". Messy code can be refactored for far less than the cost of a rewrite. The reasons matter because they define what "done" means and which screens deserve native polish first.
It also helps to be honest about the cost. For a period of time you are paying for two apps: two codebases, two sets of store listings to keep compliant, two regression passes and two sets of bugs. Product work slows down, because every new feature either waits for the new app or gets built twice. Put that period on the roadmap explicitly and agree with stakeholders on how long it is allowed to last. A migration without an end date tends to drift, and a drifting migration is the worst outcome: the old app stops improving, and the new one never quite ships.
Run two apps in parallel, deliberately
The hardest part of a migration is not the new app. It is keeping the old one alive without starving it.
Protect the legacy app
Users do not care that a new version is coming. Security updates, OS compatibility fixes, SDK upgrades demanded by store policies and urgent bug fixes still have to ship. We recommend:
- A dedicated maintenance lane for the old app with clear ownership, so it is never "whoever has time".
- A feature freeze policy: only critical or revenue-relevant features land in the old app, and each one is added to the parity list for the new one.
- Store policy tracking: target SDK level requirements and privacy declarations have deadlines, and missing one can block releases of the app you are about to replace.
Build a parity matrix
Feature parity is a list, not a feeling. Keep a matrix that both product and engineering can read:
| Area | Old app behaviour | New app status | Notes |
|---|---|---|---|
| Sign in, SSO, biometrics | Supported | In progress | Session migration required |
| Deep links | All marketing and email links | Done | Tested against link inventory |
| Push notifications | Transactional and marketing | Done | Token replacement on first launch |
| Offline documents | Cached for signed-in user | Not started | Storage migration needed |
| Analytics events | Full funnel | Partial | Event names must match |
Include the invisible features: deep link routes, analytics events, accessibility, localisation, error states and edge cases users only hit once a year. Those are what generate support tickets after launch.
Share the API layer, not the UI
The most valuable reuse in an Ionic to React Native migration is below the UI. Ionic apps are written in TypeScript, and so are well structured React Native apps. That makes a lot of logic portable:
- API clients and request/response types
- Validation rules and form schemas
- Business logic, formatting, pricing and date handling
- Feature flag definitions and remote config keys
- Design tokens for colours, spacing and typography
Move these into a shared package before or during the rewrite. Keep it free of framework imports so both apps can consume it.
// packages/core/src/api/documents.ts
// Framework-agnostic: used by the Ionic app and the React Native app
export interface DocumentSummary {
id: string;
title: string;
updatedAt: string;
status: "draft" | "signed" | "archived";
}
export function createDocumentsApi(http: HttpClient) {
return {
list: (cursor?: string) =>
http.get<{ items: DocumentSummary[]; nextCursor?: string }>("/documents", { cursor }),
get: (id: string) => http.get<DocumentSummary>(`/documents/${id}`),
};
}
The HttpClient interface is injected, so each app provides its own implementation for auth headers, retries and token refresh. Angular services and React hooks become thin wrappers around the same core.
The migration details that bite
These are the areas where migrations fail quietly, because they only show up on upgraded devices with real history.
Auth and session migration
If the new app ships under the same store listing, users will receive it as an update. They expect to stay signed in. Ionic apps often keep tokens in web storage, a SQLite plugin or a secure storage plugin, while React Native apps typically use the iOS Keychain and Android Keystore through a native module.
Write an explicit one-time migration: read the old location, validate the session with the backend, write to the new secure storage, then delete the old data. Make it idempotent and make failure non-fatal: if migration fails, fall back to a normal sign-in rather than crashing.
Push tokens
Changing the push library or registration flow can produce new device tokens. Register on first launch, send the token with user and device identifiers, and let the backend replace the previous token. Monitor delivery by app version during rollout.
Deep links
Build an inventory of every link format in use: marketing emails, transactional emails, SMS, web pages, QR codes, partner integrations. Universal links and app links must keep working with the same paths. Automated tests that open each route are cheap insurance.
Offline storage
Cached documents, drafts and preferences live in formats specific to the old stack. Decide per data type: migrate it, re-download it from the server, or accept losing it. Losing a user's unsent draft is the kind of thing that ends up in store reviews.
Analytics parity
If event names and properties change, every funnel and dashboard breaks on launch day, exactly when you need them most to compare old and new. Define the event schema in the shared package and reuse identical names.
Release trains and gradual rollout
A predictable release cadence lowers risk for both apps.
- Fixed release trains, for example every two weeks, with a code freeze and a regression pass.
- Internal and beta tracks first: TestFlight and Google Play internal or closed testing.
- Staged rollout: Google Play supports percentage rollouts, and the App Store supports phased release for automatic updates. Start small and widen as metrics hold.
- Kill switches and remote config, so a problematic feature can be disabled without a new store review.
- Clear go and no-go metrics: crash-free sessions, sign-in success, key conversion steps and support ticket volume, compared against the old app.
# Example: promote a staged Android rollout with fastlane
fastlane supply --track production --rollout 0.2 --skip_upload_apk true
Plan for store review time in your schedule, and remember that some users never update promptly. The old version will be in the wild for a while, so the backend must support both until usage drops to a level you accept.
What we would tell any team starting now
- Treat the old app as a product, not a legacy burden.
- Make parity a written matrix and include invisible features.
- Extract shared TypeScript logic early.
- Test migrations on upgraded devices with real data, never only on fresh installs.
- Roll out gradually with metrics you agreed on before launch.
If you are weighing a migration, our mobile app development team builds both Ionic and React Native apps, and our consulting service can assess whether a rewrite is worth it at all. For web-heavy products, see also our web application work.
Considering a migration or a rescue of an existing app? Contact us and we will send a free project roadmap within 24 hours, written by the senior engineers who would do the work.