Mobile — Mobile App Architecture

Modular Architecture for White-Label Mobile Apps

Direct answer

The sustainable way to build white-label mobile apps is one codebase, one core app, and a per-brand configuration contract — theme tokens, feature flags, assets, and native identifiers — injected at build time. Each brand is a build target, never a fork. With React Native and Expo, a dynamic app config selects the brand at build time while the core app reads brand values through a typed context, so shipping brand number ten costs configuration, not engineering.

White-label apps look like easy multiplication until brand three, when forked codebases turn every bug fix into three cherry-picks. The architecture decision you make before the second brand determines whether you scale or drown. This is the modular setup I use to keep many brands on one codebase.

Key facts, with sources

  • React Native's New Architecture became the default in 0.76, the legacy bridge was retired in 0.82, and Hermes V1 shipped as the default JavaScript engine in 0.84. (TO THE NEW Blog)
  • Microsoft retired Visual Studio App Center and CodePush on March 31, 2025, forcing every team that depended on it to migrate their over-the-air update architecture. (microsoft/react-native-code-push GitHub issue)
  • In the State of React Native 2024 survey, Redux drew the most negative feedback at around 18% dissatisfaction, while React's built-in state management (31% positive) and Zustand (21% positive) were the best regarded. (InfoQ)
  • Over 80% of State of React Native 2024 respondents work in teams of up to five developers, meaning most mobile architectures must be maintainable by very small teams. (SSOJet (State of React Native 2024 highlights))
  • Published production examples report Shopify at 86% unified code across its apps and Instagram sharing 85 to 99% of code between iOS and Android. (CatDoes)

Forking is the default failure mode

The tempting path is copying the repo for each new client and tweaking it. It works for exactly one release cycle. After that, every fix must be applied per fork, forks drift because each client requested one small change directly in core code, and within a year the codebases have diverged enough that merging is impossible. I have been brought in to rescue exactly this situation, and the remediation — re-unifying divergent forks — costs far more than doing it right initially.

The alternative is treating brands as data, not code. One repository, one app implementation, and a brand configuration that is the only thing differing between builds. Every architectural decision that follows exists to protect that invariant: brand differences live in config, never in conditionals scattered through feature code.

Define the brand contract as a typed interface

The heart of the system is a single TypeScript interface describing everything a brand can customize: display name, bundle identifiers, color and typography tokens, logo and asset paths, feature flags, legal copy, API tenant identifiers, and store metadata. Each brand is one object satisfying that interface, validated at build time so a missing asset fails the build instead of shipping a broken app.

The discipline is refusing customizations that do not fit the contract. When a client wants a bespoke screen, you either generalize it into a flag-controlled feature every brand could enable, or decline. The moment brand-specific code appears inside a feature — an if-statement checking the brand name — you have started a soft fork, and they compound just like real ones.

Theming through tokens, not component overrides

Brand visual identity should flow through design tokens: semantic color roles, spacing, radii, and type scale defined per brand and consumed by a shared component library. Components reference roles like primary action or surface, never hex values, so rebranding is a token file and the UI follows everywhere consistently.

Where brands genuinely need different visual structure — one wants a card grid, another a list — model it as a variant the token or flag system selects, implemented once in the shared library. Keep the escape hatch narrow: per-brand component overrides are allowed only in a designated overrides directory with a size budget. When that directory grows, it is a signal the contract needs a new capability, not that more overrides are fine.

One build pipeline, many targets

Each brand becomes a build profile: the pipeline receives a brand identifier, the dynamic app config resolves the brand object, and out come the right bundle ID, app name, icons, splash screens, and signing credentials. With Expo this is a dynamic config plus per-brand build profiles; with bare React Native it maps to Android product flavors and iOS targets or schemes.

Automate store delivery per brand from the start — screenshots, metadata, and submission — because manual store management multiplied by many brands is where operations teams quietly burn whole days per release. The goal is that releasing all brands is one pipeline run, not many afternoons.

Dynamic Expo config resolving the brand at build time
import type { ExpoConfig } from 'expo/config';
import { brands } from './brands';

const brand = brands[process.env.BRAND ?? 'default'];

const config: ExpoConfig = {
  name: brand.displayName,
  slug: brand.slug,
  scheme: brand.scheme,
  ios: { bundleIdentifier: brand.iosBundleId },
  android: { package: brand.androidPackage },
  extra: { brandId: brand.id },
};

export default config;

Testing and releasing across a brand matrix

Every brand multiplies your test surface, so be deliberate about what runs where. My split: unit and integration tests run once against the core app with a synthetic test brand exercising edge-case config values; a small end-to-end smoke suite runs per brand per release, covering launch, login, and the revenue-critical flow; and visual checks run on whichever brands have unusual token combinations.

Release trains help more than per-brand schedules. All brands ship from the same tagged commit, so support and debugging always map one version to one code state. When one client needs an urgent fix, it lands in core, and the fact that every brand gets it too is a feature of the model — one fix, many apps — not an inconvenience.

When to hire senior help

Architecture is the cheapest place to buy senior expertise, because decisions about state management, navigation, offline strategy, and update infrastructure made in week one determine costs for years. A short engagement with a senior mobile architect before or during MVP planning routinely prevents the rewrite-at-scale scenario that hits teams around their first major growth phase. If your stack includes React Native + Python + AI, a senior engineer who owns the full product beats coordinating multiple juniors.

Bottom line

Dhairya Senjaliya ships Mobile — Mobile App Architecture projects worldwide — book a scoping call to discuss your specific situation.

Common pitfalls to avoid

  • Adopting Redux with sagas and heavy boilerplate for a five-screen MVP when built-in React state or Zustand would cover the actual requirements
  • Scattering business logic inside UI components instead of isolating a data layer, making later backend changes or native module swaps expensive
  • Building deployment architecture on a hosted OTA service with no exit plan, a risk the March 2025 CodePush shutdown made concrete for thousands of teams
  • Assuming permanent connectivity and bolting on caching later, instead of designing offline storage and sync conflict resolution before the data layer hardens

Frequently asked questions

How do I handle a white-label client who wants a completely custom feature?

Generalize or decline. Build the feature once in the core app behind a feature flag, designed so any brand could enable it, and charge the requesting client for the build. Never implement it as brand-specific code inside the shared codebase — that is a soft fork, and soft forks compound until the single-codebase model collapses.

Can Expo handle white-label builds with different bundle IDs per brand?

Yes. A dynamic app config file reads a brand identifier from the environment and outputs the correct name, bundle identifier, package name, icons, and scheme per build. Combined with per-brand build profiles, each brand becomes a separate build target from one codebase, and cloud builds can produce all brand binaries from the same commit.

How many brands can one React Native codebase realistically support?

The codebase itself scales to dozens of brands if the configuration contract stays strict — brands are data, so adding one is marginal work. The practical limits are operational: store account management, per-brand review cycles, certificate renewals, and QA smoke testing. Teams that automate store delivery and releases handle large brand counts; teams doing manual releases struggle past a handful.

What state management should a new mobile app use?

For most apps, React's built-in state plus a light library like Zustand is enough; in the State of React Native 2024 survey those two drew the most positive sentiment while Redux drew the most negative at about 18% dissatisfaction. Heavier tooling is justified mainly by large teams, complex shared state, or strict audit requirements.

Do we need offline support from day one?

If users operate in the field, in transit, or in markets with unreliable networks, yes, because retrofitting offline-first sync onto an online-only data layer is one of the most expensive refactors in mobile. If the app is unusable without live data anyway, graceful error and retry handling may be sufficient.

What are over-the-air updates and should our app use them?

OTA updates push JavaScript-level fixes directly to users without waiting for app store review, which is valuable for hotfixes. Microsoft's CodePush was retired on March 31, 2025, so current options are EAS Updates, a self-hosted CodePush server, or third-party services, and updates must stay within store policies that prohibit changing an app's core purpose.

Bottom line: Dhairya Senjaliya ships Mobile — Mobile App Architecture projects worldwide. Book a scoping call at https://dhairyasenjaliya.com/#book-call.

Sources

Related guides

Keep up with new guides

New deep-dive guides on React Native, Python, and AI ship regularly. Subscribe via RSS or follow on LinkedIn.

Want help implementing this?

30-minute scoping call · Clear milestones · Senior engineer ownership