DEV Community

Cover image for The Boring React Native Stack for New Founders
Chris F A
Chris F A

Posted on • Originally published at applighter.com

The Boring React Native Stack for New Founders

Every week, first-time React Native founders ask us the same question: what stack should I use?

Here's our copy-pasted answer, with the receipts.

TL;DR

Client:   Expo SDK 54 (managed) + Expo Router + TypeScript
Styling:  NativeWind v4
Data:     TanStack Query v5 + React Hook Form
Motion:   Reanimated 4.1
Backend:  Supabase (Postgres + Auth + Storage + Edge Functions + RLS)
Payments: Stripe (payment mode, one-time)
Builds:   EAS Build + EAS Update
Enter fullscreen mode Exit fullscreen mode

No Redux. No Firebase. No custom auth. No Fastlane. No bare workflow.

Why "boring"

You have zero product-market fit information on day zero. Every stack decision you make on day zero is a bet without data. The correct bet is the one that:

  1. Has the most Stack Overflow answers
  2. Has the shortest "hello world → first user" path
  3. Has the lowest switching cost when you learn you were wrong

That's a boring stack, definitionally.

The client

Expo SDK 54 (managed workflow)

npx create-expo-app@latest my-app
cd my-app
npx expo start
Enter fullscreen mode Exit fullscreen mode

Don't expo prebuild. Don't eject. The managed workflow gives you EAS Build (cloud iOS builds without a Mac), expo-updates for OTA JS pushes, and pre-wrapped native modules for 90% of what you'll need.

Expo Router

File-based routing. If you know Next.js App Router, you know Expo Router.

app/
├── (tabs)/
│   ├── _layout.tsx
│   ├── index.tsx
│   └── profile.tsx
├── notes/
│   └── [id].tsx
└── _layout.tsx
Enter fullscreen mode Exit fullscreen mode

Deep links, universal links, and typed routes come for free. Docs:

NativeWind v4

Tailwind for React Native.

<View className="flex-1 bg-white dark:bg-neutral-900 px-4">
  <Text className="text-2xl font-semibold text-neutral-900 dark:text-white">
    Hello
  </Text>
</View>
Enter fullscreen mode Exit fullscreen mode

Your team already knows Tailwind. Move on.

Data layer

TanStack Query v5 for anything that touches the network

const { data, isPending, error } = useQuery({
  queryKey: ['notes', userId],
  queryFn: () => supabase.from('notes').select('*').eq('user_id', userId),
})
Enter fullscreen mode Exit fullscreen mode

Cache invalidation, background refetch, optimistic updates, offline persistence — all included. You do not need Redux.

React Hook Form for forms

const { control, handleSubmit } = useForm<Note>()

<Controller
  control={control}
  name="title"
  render={({ field }) => (
    <TextInput onChangeText={field.onChange} value={field.value} />
  )}
/>
Enter fullscreen mode Exit fullscreen mode

Boring. Fast. Good TypeScript types. The end.

Motion: Reanimated 4.1

const offset = useSharedValue(0)
const animatedStyle = useAnimatedStyle(() => ({
  transform: [{ translateX: offset.value }],
}))

const gesture = Gesture.Pan().onUpdate((e) => {
  offset.value = e.translationX
})
Enter fullscreen mode Exit fullscreen mode

Worklets run on the UI thread. 60fps by default. No bridging.

The backend: Supabase, all of it

The reason Supabase is boring in the good way: it's Postgres. When your query is slow at 11pm, you open the SQL editor and read the plan.

Row-Level Security (non-negotiable)

Enable RLS on every table before the table has data:

alter table notes enable row level security;

create policy "notes_owner_select"
  on notes for select
  using (auth.uid() = user_id);

create policy "notes_owner_insert"
  on notes for insert
  with check (auth.uid() = user_id);
Enter fullscreen mode Exit fullscreen mode

Now no client bug, no leaked JWT, no misconfigured API route can leak user A's rows to user B. The database refuses. This is the single highest-leverage security decision you make.

Edge Functions for secrets

Every third-party API key (OpenAI, Anthropic, Stripe, Deepgram) lives in an Edge Function. Zero secrets in the client bundle:

Deno.serve(async (req) => {
  const apiKey = Deno.env.get('OPENAI_API_KEY')!
  // ... call OpenAI, stream response back via SSE
})
Enter fullscreen mode Exit fullscreen mode

Payments: Stripe (one-time, not subscriptions)

First-time founders default to subscriptions because that's what YC tells them to want. Ship one-time first — simpler dunning, faster checkout, no churn number to obsess over on Monday mornings.

const session = await stripe.checkout.sessions.create({
  mode: 'payment',
  line_items: [{ price: priceId, quantity: 1 }],
  success_url: `${origin}/success?session_id={CHECKOUT_SESSION_ID}`,
  cancel_url: `${origin}/cancel`,
})
Enter fullscreen mode Exit fullscreen mode

Webhook → product_licenses table → grant unlocked. Applighter itself ships in this exact pattern — every template in the catalog uses this one-time-purchase flow end-to-end.

Builds: EAS

eas build --platform ios --profile production
eas submit --platform ios --latest
eas update --branch production --message "fix: crash on launch"
Enter fullscreen mode Exit fullscreen mode

Cloud iOS builds. No Mac required. OTA updates ship in minutes instead of days.

Comparison table

Piece Boring pick Trendy alt Why v1 skips trendy
Framework Expo managed Bare RN 2 weeks re-wrapping native libs
Styling NativeWind Tamagui Your team knows Tailwind
State TanStack Query + useState Redux / Zustand 95% of state is server state
Backend Supabase Firebase Firestore's query model is a trap
Auth Supabase Auth Clerk It's already bundled with the DB
Payments Stripe one-time RevenueCat subs Subs are v2
Builds EAS Fastlane Cloud-native, no Mac needed

What we deliberately omit

  • Redux / Zustand — see above
  • GraphQL — Postgres + PostgREST is enough
  • Sentry — recommended, but not day one
  • Monorepo — great for 3+ apps, overkill for one
  • Detox / Maestro — unit tests first, E2E after 6 months

The AI stack question

"But my app is an AI app — does the stack change?"

Almost not at all:

+ Supabase Edge Function (holds OPENAI_API_KEY)
+ Postgres table for chat history (RLS on)
+ SSE streaming back to client (rendered with Reanimated)
Enter fullscreen mode Exit fullscreen mode

That's it. AI features are just features.

The playbook

  1. npx create-expo-app on latest stable SDK
  2. Add NativeWind + Expo Router + Reanimated + TanStack Query + RHF
  3. Supabase project → schema in migrations → RLS before any rows
  4. Every third-party key in Edge Functions
  5. Stripe payment mode → webhook → product_licenses table
  6. EAS → TestFlight in the first two weeks
  7. Ship features. Reconsider stack in 18 months.

Or: skip steps 1–5

Our templates are this exact stack, wired end-to-end, with real Stripe flow and RLS on every table — the catalog is linked up in the payments section. If you'd rather start on hour one with a working app instead of hour thirty, that's what they're for.

Either way — pick a boring stack and ship. Reading yet another stack post is the enemy.

What's the stack you actually shipped with — and what would you cut from this list? Drop a comment.

Top comments (0)