The Goal
Ship a working iOS + Android app in 4–6 weeks. Not a prototype. A real binary in TestFlight and Google Play Internal Testing, ready to hand to design partners.
This is the exact stack I use for Tier 2 client MVPs. It's optimized for speed without painting yourself into a corner.
The Stack
- Framework: React Native + Expo SDK 53
- Language: TypeScript (strict mode, always)
- Router: Expo Router (file-based, drop-in replacement for
react-navigation) - State: Zustand for UI state, TanStack Query for server state
- Forms: React Hook Form + Zod
- UI: Custom components on top of NativeWind (Tailwind for RN) or
react-native-paperif you want Material out of the box - Backend: Supabase or Firebase (auth + DB + storage in one)
- Analytics: Posthog React Native SDK
- Crash reporting: Sentry
- Builds: EAS Build (cloud)
- OTA updates: EAS Update
That's it. No Redux. No custom build pipeline. No native modules unless absolutely required.
Project Setup in 10 Minutes
npx create-expo-app@latest my-app -t default-typescript
cd my-app
npx expo install expo-router react-native-screens react-native-safe-area-context
npm install zustand @tanstack/react-query react-hook-form zod
npm install nativewind tailwindcss
npx tailwindcss initConfigure Tailwind for NativeWind (tailwind.config.js needs content: ["./app/**/*.{js,jsx,ts,tsx}"]), wrap your root layout in a QueryClientProvider, and you're rendering.
Folder Structure That Doesn't Become a Nightmare
app/
(auth)/
login.tsx
signup.tsx
(tabs)/
index.tsx
profile.tsx
_layout.tsx
components/
forms/
layout/
ui/
lib/
api/
hooks/
stores/
utils/
constants/
types/
Two things matter:
- Group routes by access layer using Expo Router groups:
(auth)for logged-out screens,(tabs)for the main tabbed app. - Keep
lib/api/separate from screens. A screen should never callfetchdirectly. All network calls go through a typed query/mutation hook.
The Auth Pattern
Supabase or Firebase, your choice. Both work the same way at the surface:
- Wrap the app in an
AuthProviderthat exposesuser,loading,signIn,signOut. - The root
_layout.tsxreadsuserand decides whether to show(auth)or(tabs). - Use
expo-secure-storefor token persistence, neverAsyncStoragefor credentials.
Skip building your own JWT refresh logic. Both Supabase and Firebase SDKs handle it.
Forms: Don't Roll Your Own
React Hook Form + Zod is the only setup I use. Define a schema, infer types, validate on blur.
const loginSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
type LoginInput = z.infer<typeof loginSchema>;
const { control, handleSubmit, formState } = useForm<LoginInput>({
resolver: zodResolver(loginSchema),
});This pays for itself the first time you change a field name and TypeScript catches every screen that depended on it.
Server State with TanStack Query
export function useListings() {
return useQuery({
queryKey: ['listings'],
queryFn: () => api.listings.list(),
staleTime: 60_000,
});
}The benefits: caching, refetch on focus, optimistic updates, retry logic - all free. The first MVP I built without TanStack Query, I burned a week rebuilding 60% of it from scratch.
Push Notifications That Actually Work
Use expo-notifications. The 10-minute version:
- Request permission on first launch (after a clear in-app prompt explaining why).
- Get the Expo push token.
- Store it in your backend, associated with the user.
- Send via Expo's push service from a server endpoint.
Done. No FCM key wrangling, no APNs certificates. Expo handles both stores.
EAS Build: Don't Touch Xcode
EAS Build runs in the cloud, hands you a .ipa and .aab in 15 minutes, and submits to both stores via eas submit. You will not open Xcode for an MVP. You will not generate provisioning profiles by hand. This alone saves a week per project.
npx eas build --profile preview --platform all
npx eas submit --platform iosOTA Updates Without App Store Review
Bug in production? EAS Update pushes a new JS bundle in under a minute. No Apple review for JS-only fixes. The native shell stays the same, the JS layer updates next time the user opens the app.
You still need a real store submission when you change native dependencies or app metadata. For everything else, OTA is your friend.
What to Skip on an MVP
- Custom navigation animations
- Dark mode (ship light, add dark in v1.1)
- Tablet layouts (build for phone first)
- Internationalization (unless your launch market needs it Day 1)
- Custom splash screens - the Expo default is fine
- Onboarding tooltips and tours
- Settings screens beyond "log out" and "delete account" (the second is required by Apple)
- Detailed analytics dashboards inside the app
Add these in v1.1 based on what users actually ask for.
The 4-Week Schedule
| Week | What ships |
|---|---|
| 1 | Project setup, auth, basic navigation, design tokens |
| 2 | Core feature 1 fully working end-to-end |
| 3 | Core feature 2 + push notifications + crash reporting |
| 4 | Polish, TestFlight + Internal Testing submission, design partner onboarding |
If you're not in TestFlight by end of week 4, the scope was too big. Cut features, don't add hours.
Common Mistakes I See
- Building a full design system before any feature works. Build the feature with rough components first. Refactor into a system in v1.1.
- Picking native iOS modules early. Wait until you've hit the actual limitation. 90% of the time, the JS solution is fine.
- Underestimating the App Store review process. Budget 5 business days for the first review, including a near-certain rejection over a missing privacy detail.
- Ignoring the Delete Account requirement. Apple requires it. Build it in week 4, not after rejection.
The 5% You'll Need a Native Module For
Bluetooth, ARKit, HealthKit, complex camera processing, background audio. For all of these, check expo-modules first. If it's not there, you'll need to drop to native code or wait until v2.
Related Reading
- React Native vs Flutter in 2026 - the framework decision
- Sharing code between web and mobile - if you also have a web product
- What a mobile app actually costs in Canada - real budget ranges
Want Help Shipping It?
I do this for a living. If you want a fixed-price quote to take your MVP idea to TestFlight in 4–6 weeks, book a call.
Get practical engineering insights
AI voice agents, automation workflows, and shipping fast. No spam, unsubscribe anytime.