Cross Platform Modernization
A shared codebase three major versions behind is not saving you anything. It is one store deadline away from being two apps you cannot ship.
- Typical duration
- Four to twelve months
- Team shape
- Two to five senior engineers
- Starts with
- A three-week assessment

The upgrade treadmill is the price of a shared codebase
React Native and Flutter both move quickly, and a codebase that skips upgrades compounds the problem: each deferred version makes the next one harder, native dependencies fall out of maintenance, and eventually a store requirement lands on a framework version that cannot meet it.
The way out is not a rewrite. It is a sequence: restore the ability to build and ship, then move the framework one version at a time with the app verified in the field at each step, then deal with the dependencies that were blocking it. Feature work continues throughout, and the end state is a codebase where upgrading is routine rather than a project you have to schedule.
What usually goes wrong, and what we do instead
The problem compounds quietly, and then arrives with a deadline attached.
Where shared codebases fall behind
- An upgrade deferred once, then repeatedly, until the gap is a project
- A native dependency abandoned upstream and blocking every version bump
- A build that only works on one machine belonging to someone who left
- No tests, so nobody can tell what a version bump actually broke
- Platform conditionals everywhere, so the shared code is not really shared
How we run it instead
- The ability to build and ship restored before anything else is touched
- One framework version at a time, verified in the field at each step
- Abandoned dependencies replaced or brought in-house and owned
- Characterisation tests written before behaviour is changed
- The boundary redrawn deliberately, so conditionals stop accumulating
What changes, in practice
Not a version number. The things that are different for the people who build and ship the app.
The codebase today
Working, and stuck.
- The framework is several major versions behind
- An upgrade attempt was tried once and abandoned
- Some dependencies have no maintained upgrade path
- The build works on one machine and nobody is sure why
- There are no tests, so a version bump is an act of faith
- A store deadline would be an emergency for both platforms at once
The codebase after
Same product, current.
- The framework is on a supported version, with a cadence agreed
- Upgrading is routine maintenance rather than a project
- Every dependency is maintained, replaced, or owned by you
- The build runs from a clean checkout in CI, reproducibly
- Behaviour is pinned by tests written from the original app
- Store requirements are tracked, so a deadline is never news
What the work involves
Modernizing a shared codebase is five activities, in a deliberate order.
Restoring the release path
A reproducible build in CI producing both store artefacts, before anything else is touched.
Characterisation
Tests written against what the app actually does, so a version bump has something to fail against.
Dependency recovery
Abandoned packages replaced, or brought in-house and owned, rather than pinned indefinitely.
Framework upgrades
One major version at a time, each shipped and verified in the field before the next begins.
Boundary repair
Platform conditionals pulled back behind a documented native boundary rather than left scattered.
Device range recovery
Re-established support for the hardware your users carry, tested rather than assumed.
How the programme runs
The same five stages as every engagement, applied to a codebase that cannot stop shipping.
Diagnose
Whether you can ship today, how far behind the framework is, and which dependencies block the next version.
Decide
The upgrade path, the dependency decisions and the order of work, sequenced by risk rather than appetite.
Prove
One release shipped from a clean CI build to both stores, before any version is changed.
Deliver
Versions and dependencies moved in slices, each verified in the field before the next.
Hand over
Your team performs an upgrade with us watching, then without us, on the cadence we agreed.
What we work with
The starting point is whatever is already there. This is the range we bring codebases up to.
Shared
- React Native
- Flutter
- TypeScript
- Expo
Native
- Kotlin
- Swift
- Gradle
Data and services
- SQLite
- Firebase
- PostgreSQL
- GraphQL
Delivery
- GitHub Actions
- TestFlight
- Sentry
- Datadog
What you get, and when
Handed over as it is produced, not assembled at the end.
- Whether you can ship today, and what it would take to
- The upgrade path, version by version, with what blocks each
- A decision per unmaintained dependency: replace, own, or remove
- A reproducible build running in CI from a clean checkout
- Characterisation tests pinning the original behaviour
- A staged release to both stores you can halt
- A framework upgrade cadence your team can actually follow
- Crash and performance dashboards split by platform and version
- The redrawn boundary, documented, with what belongs on each side
The rest of our cross-platform work
Shared-codebase engagements overlap more than most. These are the neighbouring ones.
Related work
Engagements where the product could not stop shipping while it was repaired.
What we work towards
ILLUSTRATIVE, pending sign-off. These are the measures this work is judged on and the direction they should move — not results from a named engagement. They will carry real figures or be removed before launch.
Framework versions behind
Time to ship a hotfix
Unmaintained dependencies
Machines that can build a release
Behaviour covered by tests
Questions about cross-platform modernization
The ones we are asked most often before a first conversation.
How is this different from Cross Platform Migration?
Would it be quicker to start again?
We cannot ship a release at all right now. Where do you start?
One of our dependencies is abandoned. What happens to it?
Do we have to pause feature work?
How do we avoid ending up here again?
Shared codebase that has fallen behind?
Tell us what version you are on and whether you can ship. We will tell you what the path back looks like.

