The first day of work on an app modernisation project surprisingly often looks like this: The agency that built it no longer exists. The build last ran on a laptop that won’t start up anymore. The certificates have expired, and the README is just two lines long. And a countdown has been running in the Play Store since 31 August, after which the app will become invisible to new customers.
In this situation, it’s very easy to say, ‘Well, let’s just rewrite it’, because a normal app update no longer seems possible anyway. This describes the most expensive decision you can make for your app. And sometimes the only right one. That’s precisely why ‘rewrite or refactor?’ the wrong question. It forces you into two extremes: twelve months without visible progress on the one hand, and a codebase that nobody wants to touch anymore on the other.
The timing of the decision is now determined by the app stores anyway.
Why your app needs an update now, whether you like it or not
An app that hasn’t been updated for two years falls behind because the ground beneath it is shifting. The platforms set the deadlines, and they bring them forward every year.
Since 31 August 2026, Google has required new apps and app updates on the Play Store to have Android 16 (API level 36) as their target SDK. Existing apps must target at least API level 35; otherwise, they will no longer be visible to new users on current devices in the Store. The extension period expires on 1 November 2026 (Google Play, Target API Level Requirements).
Furthermore, from 1 November 2025, all new apps and updates targeting Android 15 or higher must support 16-KB memory pages, which will require recompilation for native code and older third-party libraries (Android Developers Blog).
Apple is following suit. From 28 April 2026, App Store Connect will only accept builds created with Xcode 26 and the iOS 26 SDK (Apple Developer, Upcoming Requirements). This may sound like a minor issue until your project is running on a version of Xcode that no longer launches on the latest macOS, and the dependencies from 2021 no longer compile under the new SDK.
And then there are the frameworks themselves. Microsoft discontinued support for Xamarin on 1 May 2024; the official route is now .NET MAUI (Microsoft). React Native froze its legacy architecture in June 2025; since version 0.82, the framework has run exclusively on the New Architecture, and Expo SDK 55 no longer allows downgrading (Expo documentation). Anyone running a React Native app from 2021 is therefore faced with an architectural migration of every single native module – not just a standard version upgrade.
So the question isn’t whether you need an app update. The question is how big it will be and whether you’ll still be able to schedule it yourself.
The myth of a clean slate
Every developer who opens up someone else’s codebase has the same reflex: ‘This is rubbish; we’ll rewrite it.’ Joel Spolsky described this reflex back in April 2000 as the ‘biggest strategic mistake a software company can make’ (Things You Should Never Do, Part I). His example was Netscape, which completely rewrote its browser and failed to release a new major version for almost three years, whilst Internet Explorer took over the market.
The crux of his argument still applies to apps today. Old code looks ugly because it works. Every odd bit of special handling is a bug fix prompted by a real user on a real device.
The workaround for the camera API on a specific Samsung model, the date parsing that behaved differently on an old version of iOS, the retry that a corporate client with poor Wi-Fi in their warehouse needed. A rewrite discards this knowledge and rebuilds it under time pressure, including the bugs that were already fixed five years ago.
Added to this is the pitfall that strikes in every rewrite project: feature parity. The new app can’t go live until it can do everything the old one can. So the team spends twelve months recreating what they already have, whilst the old app still needs to be maintained because customers are still using it. Two codebases, one team, zero new features. In 2004, Martin Fowler described the Strangler Fig approach specifically for this scenario – replacement by gradual overgrowth rather than a Big Bang (Strangler Fig Application). More on that in a moment.
Spolsky wasn’t right in every case, though. There are codebases you shouldn’t try to save. To recognise them, you first need to understand what refactoring can actually achieve.
When refactoring is enough
Refactoring means that the behaviour remains the same whilst the structure changes. You swap dependencies, raise the target SDK, extract business logic from the views, and rewrite tests for the parts that break most frequently. The app remains ready for release at all times.
This works when three things come together. Firstly, the framework is active. Swift, Kotlin, Flutter and React Native on the New Architecture are actively maintained and have a clear upgrade path.
Secondly, your team can change the code without fear. If every change to the product list breaks three other screens and nobody can say why, that’s a crisis. Thirdly, the architecture is sound. A clean separation of UI, logic and data access can be modernised. An app where all the business logic resides in the view controllers can only be moved.
Maintenance eats up a large proportion of development time anyway; in a survey by SonarSource, the average was around 30 per cent (SonarSource, 2019). Refactoring determines how much of this third will be productive in future and how much will remain a patchwork solution.
For most apps, refactoring is the right approach. It’s unspectacular, it doesn’t make the headlines, and it costs a fraction of what a complete rebuild would. An honest assessment – ideally carried out by someone who didn’t write the code – will reveal whether your app is a suitable candidate. The free Legacy Check provides an initial assessment in two minutes: a risk score plus a recommendation for refactoring, a ‘strangler’ approach or a complete rebuild. The same assessment also shows when it’s no longer enough.
When a rewrite is inevitable
There are four situations in which you should stop refactoring and start building from scratch.
The first: the framework is dead. Xamarin apps, Cordova apps, React Native apps on version 0.6x with twenty native modules from repositories that haven’t seen a commit in years. In this case, the ‘upgrade’ is effectively a rewrite of the bridge to the operating system anyway. So you might as well build it on a stack that will last for the next ten years.
The second sounds absurd, but it’s the scenario from the introduction: nobody can build the app. The Apple Developer Account is linked to the private Apple ID of a developer who left three years ago. The signing certificate has expired, the keystore for Android is on a laptop that no longer boots, and the build pipeline was a shell script that only ran on that exact machine.
If the first day of the modernisation project involves simply compiling the app at all, the question is whether the app still belongs to you. Often, building a new one from scratch is cheaper than digging through the ruins.
The third: the architecture is the problem. An app that started out as a prototype and was then ‘extended’ in production for five years often lacks any distinct layers to work with. Business logic in the UI, network code in the components, state in global variables. Refactoring would end up affecting almost every file anyway.
The fourth: The business model has changed. The app used to be a catalogue and is now expected to handle payments, bookings, work offline and integrate with the ERP. When the majority of the requirements change, you’re no longer modernising – you’re building a new product. In this case, the rules from our guide on costs and the app development process apply.
In all four cases, a rewrite is only the right decision if you treat it as a product project. That means: a scope that is smaller than the old app, because a smaller scope avoids the feature parity trap. An MVP that has real users after three months. And a willingness to scrap features that nobody has used for two years, according to the analytics.
The third way: phasing out the app step by step
Between ‘keeping everything’ and ‘throwing everything away’ lies the approach that suits most apps in small and medium-sized enterprises: gradual replacement following the Strangler Fig pattern. The strangler fig, after which Fowler named the pattern, grows around a tree until it stands on its own and the old trunk can disappear. Applied to an app: new screens are created in the new stack, old screens remain until it’s their turn, and both run within the same app.
Technically, this is known as brownfield integration. A React Native screen runs as its own view controller within an existing Swift app, a Flutter module is integrated as an AAR into a Kotlin app, and the navigation system takes the user back and forth between the old and new systems. The pitfalls lie at the interfaces: two navigation systems must agree on who controls the back button, the state must be passed over cleanly during transitions, and the app size increases as long as both stacks are on board.
The order in which you proceed determines success. You start with the screen that changes most frequently, not the one that’s easiest, because any work done in the old stack each week is work lost there. The login screen, which nobody has touched since 2019, can wait. The product detail page, which someone works on every sprint, is the first to be migrated.
Shopify has demonstrated this approach over five years. At the end of January 2020, the company announced it would make React Native the foundation of its mobile development, and in April 2025 it took stock: screens load in under 500 milliseconds at the 75th percentile, and where hardware access, widgets with tight memory budgets or long background jobs are involved, the code remains deliberately native (InfoQ, April 2025).
The key point of this story is not the choice of framework. It is that the migration never resulted in a release halt. The apps continued to be deployed throughout.
Since September 2026, the story has entered a new chapter: Shopify is bringing its major apps back to Swift and Kotlin. The Shop app was rebuilt natively in twelve weeks; the reasoning is that AI coding agents now handle enough of the implementation, translation and testing work that having a single shared codebase is no longer a valid argument (Shopify Engineering, September 2026). This does not contradict the ‘strangler’ approach; it confirms it. A twelve-week rewrite is only possible if the old codebase is sound, tested and ready for deployment right up to the very last day. Shopify was able to rebuild because nobody allowed the project to come to a standstill for five years. And it shows that framework decisions have a half-life. Your architecture should allow for the next change, whatever that may be.
This approach comes at a cost, and you should be aware of it before you decide to go ahead. It requires a clean API layer between the app and the backend, so that the old and new screens see the same data. Feature flags are essential for initially activating new screens for just ten per cent of users; without them, one hundred per cent of your customers will see the crash caused by the new screen on a five-year-old Android device.
Above all, however, the old codebase needs a fixed end date. Without one, in three years’ time you’ll be permanently running two stacks, with two build pipelines, two sets of dependencies and a team that has to be at home in both worlds. That’s the most expensive scenario of all, more expensive than any rewrite.
The decision in a table
The three questions from the ‘Direct Answer’ section, supplemented by requirements, release readiness and time horizon, will help structure the discussion with senior management in ten minutes.
| Criterion | Refactoring | Gradual replacement | Rewrite |
|---|---|---|---|
| Framework status | maintained, clear upgrade path | maintained, but architectural break (e.g. RN Legacy → New Architecture) | support discontinued (Xamarin, Cordova) or no community |
| Team confidence | Changes can be planned, tests in place | Core modules understood, peripheral areas not | No one can build the app reliably |
| Architecture | Layers separated, logic testable | Partially separated, individual modules can be decoupled | Logic in the UI, no layer to build upon |
| Requirements | Stable, focus on maintenance | Growing in individual areas | Changing fundamentally (new business model) |
| Ability to release whilst working | at any time | at any time, screen by screen | only with the MVP |
| Typical timeframe | Weeks to a few months | 6–18 months, delivered on an ongoing basis | Big Bang: 6–12 months without a release; MVP scope: first release after approx. 3 months |
If most of your entries fall into the middle column, that’s the norm. Most apps in small and medium-sized enterprises are neither hopeless nor in perfect shape; they lie somewhere in between. With a one- to two-week code audit, every row in the table can be filled with findings rather than gut feelings.
Which stack after modernisation?
Modernisation is needed for what is blocking updates, not for what is currently receiving less applause at conferences. A well-maintained Kotlin or Swift app isn’t a candidate for modernisation simply because the agency next door recommends Flutter; in such cases, refactoring to Jetpack Compose and SwiftUI is the natural path, and store submission deadlines are sorted with a simple toolchain update.
If, on the other hand, the app comes from a defunct cross-platform stack, the path almost always leads to Flutter or React Native, because both offer a single codebase for iOS and Android and can be embedded into existing native apps. Which of the two suits your team best depends primarily on your existing web development expertise. Bear in mind that AI coding agents are currently driving down the cost of maintaining two native codebases, with Shopify’s return to Swift and Kotlin being the first major example of this. You can find a detailed comparison in our article Flutter vs. React Native 2026.
Frequently asked questions about app modernisation
How can I tell if my app needs an update before the app store hides it?
Three signs. Your developer needs more than a few days to update the target SDK, the crash rate rises after every new OS version, and dependencies in the project haven’t been updated for over two years. Each of these signs is normal on its own. All three together mean that the next app update will be a major project.
Can I modernise an app without users noticing?
Yes, and with refactoring and a phased rollout, that should actually be the aim. Users should notice faster screens and fewer crashes, not a new user interface. The temptation to overhaul the design at the same time as modernising doubles the scope and obscures whether problems stem from the new technology or the new design.
Is a Progressive Web App an alternative to an app update?
For apps that primarily display content and do not require deep hardware integration, a PWA can completely bypass app store review times. Web push notifications now also work on iOS. However, as soon as Bluetooth or NFC access, reliable background processes, camera workflows or offline payments form part of the core functionality, a native or cross-platform app remains the better choice.
Conclusion: Asking the right question
The right question is: Which part of the app is blocking the next update, and how do we replace precisely that part without jeopardising the rest? Sometimes the answer is an afternoon spent updating dependencies. Sometimes it’s a complete rebuild with a smaller scope. Most often, it’s a series of releases in which the new app grows around the old one until nothing remains of the old codebase.
The app store deadlines take the pressure off you when it comes to deciding when to act. The direction is still up to you. If you don’t want to make the decision on your own, let’s take a look at your app together: App development at nextlevels.