Most mobile codebases become hard to change for the same three reasons: state is spread across components, navigation is entangled with business logic, and the data layer was never really designed. None of these hurt at launch. All of them determine how expensive feature work is by month twelve.
01Which decisions are actually expensive to reverse
Some choices are cheap to change later and get far too much attention; others are structural and get almost none.
The navigation structure is expensive because every screen depends on it. The data-fetching and caching layer is expensive because every feature touches it. The state model is expensive because it determines what a component is allowed to know.
Styling approach, component library, folder structure and even the UI framework within a platform are comparatively cheap. Teams spend their design energy inversely to this.
02State: the rule that keeps working
Separate server state from client state and stop treating them as the same problem.
Server state is data you do not own - it is cached, it goes stale, it needs revalidation, retry and invalidation. A data-fetching library handles this properly and removes most of what people put in global stores.
Client state is genuinely local: form input, selected tab, modal visibility. Most of it belongs in the component that owns it. What remains that is truly global - session, theme, feature flags - is a small surface, and once server state is handled separately you usually find you barely need a global store at all.
- Server data belongs in a caching layer with explicit staleness rules, not a global store you mutate by hand.
- Keep client state at the lowest level that works; lifting state early is the most common source of unnecessary re-renders.
- Global state should hold things whose lifetime is the session, not things that happen to be needed in two places.
- Never derive the same value in two places - derive once and pass it, or the two will diverge under a race.
03Navigation is a public interface
Treat routes as a contract. Once you support deep links, push notification targets and any web-to-app handoff, your navigation structure is an external interface that other systems depend on.
That has a design consequence: screens should be reachable from a URL-shaped description with parameters, not only from a specific in-app path. Screens that can only be reached by navigating through three other screens cannot be linked to, which breaks notifications, marketing links and restoration after a crash.
Keep business logic out of navigation handlers. The moment a route decides whether a user is entitled to something, you cannot deep-link without duplicating that check.
04The layer most teams skip
A defined data layer - one place that knows how to talk to your API, apply auth, handle retries, normalise errors and shape responses - is the difference between changing an endpoint in one file or twenty.
Without it, fetch calls spread into components and every backend change becomes a search-and-replace across the app. This is the most common structural failure we find in codebases we take over, and it is comparatively cheap to introduce early and expensive to introduce late.
Topics
Lena Voss
Lead Architect · SyncTrix
Writes about the engineering decisions behind production systems - architecture, delivery and the trade-offs that only show up at scale.
Building something like this?
SyncTrix engineers AI, SaaS, platform and cloud systems for enterprises and high-growth teams. Tell us what you're shipping and we'll scope it with you.
Talk to an engineer