Growth: charge for it.
- Users
- Hundreds to tens of thousands
- Team
- Two to five engineers, each with agents
You are here if
- Users keep old versions for weeks after a release
- A store rejection costs you a launch date
- You need analytics, crash reporting and push that you own
- The backend is your code, shared with a web product or not
Growth on mobile is about release discipline more than architecture. The app is still one Expo project, but the backend is now yours, the API is versioned, and a Maestro flow runs on a real device before every build. The agent keeps working in the JavaScript layer; native changes go through a config plugin and a full build, and are rare by design.
What changes when you leave this tier: a native capability with no good bridge, or enough engineers that per-platform teams become cheaper than a shared layer. Until then, stay cross-platform.
Consumer · content & social
Expo + EAS + your own typed API
The same Expo app, now behind a versioned API you own, with device tests and crash reporting before every build.
The stack
- Framework
- Expo + Expo Router, EAS Build and Update
- Data
- Your API (Hono or NestJS) over Postgres, with a CDN for media
- Auth
- Your provider, with refresh tokens handled in one place
- Hosting
- EAS for builds and updates, store releases on a cadence
- Also
- Sentry for crashes, PostHog for analyticsMaestro flows on real devices in CIPush through your API, not straight from a hosted backend
Why agents do well here
4/5- A versioned API with generated types lets the agent change the backend without breaking the users still on last month's build.
- Maestro flows are YAML the agent can read and extend, turning "does the feed still scroll" into a check it runs itself.
- Everything is still TypeScript; native changes go through config plugins and remain rare.
Avoid at this tier
- Breaking API changes; add fields, never remove them, until the old version's usage is zero.
- A custom native module for performance you have not measured with the profiler.
- Shipping a store build without the Maestro flows passing.
Growth moves the backend into your hands and the release process into CI. The app is still Expo, but every API call goes through a versioned, typed client, and every build runs device flows before it is submitted.
apps/mobile/ app/, src/features/, maestro/ (flows)
apps/api/ your backend with /v1 routes and OpenAPI
packages/api-client/ generated per API version
First five decisions: versioned API paths, generated client, Sentry and analytics before launch, Maestro on the three flows that matter, and a release cadence that is a calendar, not a reaction.
Companion · mobile face of a SaaS
Expo + shared packages + EAS
The monorepo grows up; the mobile app gets its own release pipeline while still sharing every type with the web app.
The stack
- Framework
- Expo + Expo Router in the product monorepo
- Data
- The product API, consumed through a shared generated client
- Auth
- The product's provider with native SDK and biometric unlock
- Hosting
- EAS Build and Update with a release channel per environment
- Also
- Shared design tokens through NativeWindMaestro flows for the mobile-only pathsDeep links mapped to the web app's URLs
Why agents do well here
5/5- The shared client and schema packages mean the agent's backend change surfaces as a type error in the mobile app before anyone opens a phone.
- Deep links that mirror web URLs let the agent reuse the web app's route knowledge for mobile navigation.
- Release channels per environment make it safe for the agent to push updates to staging on every merge.
Avoid at this tier
- Diverging the mobile data model from the web one; keep one schema package.
- Rebuilding web features on mobile because they exist; keep the phone flows deliberate.
- Native modules that only one platform needs, unless it is the product.
The companion app now has its own release pipeline but remains a consumer of the product’s API and schema. Deep links map to web URLs so the same notification or email works on either surface.
apps/web/
apps/mobile/ app/, src/features/, maestro/
apps/api/
packages/schema/ packages/api-client/ packages/tokens/
First five decisions: one schema package, deep links equal web URLs, release channels per environment, biometric unlock on top of the shared auth, and Maestro on the mobile-only flows.
Tool · offline-first utility
Expo + SQLite + optional sync
Local-first stays the default; sync, accounts and paid tiers are added as separate features with their own boundary.
The stack
- Framework
- Expo + Expo Router, EAS Build and Update
- Data
- expo-sqlite with Drizzle on device; PowerSync or a small Hono API for sync
- Auth
- Only for sync and purchases
- Hosting
- EAS; RevenueCat for in-app purchases
- Also
- NativeWindMaestro flows for the core workflowSentry
Why agents do well here
4/5- Keeping local data as the source of truth means the agent can test the whole workflow without a network.
- Sync as a separate feature with its own module keeps the hardest code in one place the agent can read in isolation.
- Purchases through RevenueCat give the agent a documented SDK instead of two platforms' store APIs.
Avoid at this tier
- Rewriting the app around a server database when sync is requested; add sync to the local one.
- Handling store receipts yourself.
- Sync engines you cannot debug from the client; prefer ones with visible queues.
The tool now pays for itself, so purchases and optional sync appear. Both are separate features behind a small boundary; the core workflow still works offline and is still what the device tests cover.
app/ index.tsx, item/[id].tsx, settings/, paywall.tsx
src/db/ schema.ts, migrations/, client.ts
src/features/ <workflow>/, sync/, purchases/
maestro/ core-workflow.yaml
First five decisions: local SQLite stays primary, sync as its own module, RevenueCat for purchases, accounts only when sync is on, and Maestro on the offline path first.