Seven React Native templates in one year, all on Expo SDK 54 + Supabase + NativeWind. This is the technical retrospective — seven lessons with code.
The templates
- Weather ($49, 5 screens)
- Fitness ($89, 12+ screens)
- LearnHub ($89, 9 screens)
- RideNow ($99, 8 screens)
- Food Delivery ($79, 5 screens)
- AI Voice Notes ($79, 16+ screens) — Whisper
- Chat with PDF ($79, 16+ screens) — RAG
All share one substrate. That's the whole point.
Lesson 1 — Build the substrate before the first template
Weather was our first template. Five screens, no auth, no DB. It didn't need Supabase. We built it against Supabase anyway.
Rationale: template two would need auth, RLS, edge functions, and a marketing page contract. Building that infra for a template that didn't need it looked wasteful for two weeks. It paid for itself by template three.
If you want to build a template business, spend the first sprint on infra you won't use. It's the least glamorous advice I have.
Lesson 2 — API keys never live in the RN bundle
Both AI templates route through Supabase Edge Functions. The customer's OpenAI or Anthropic key sits at the edge. Client calls the function; function holds the key; RLS enforces per-user rate limits.
// Bad: key in the client
const openai = new OpenAI({ apiKey: EXPO_PUBLIC_OPENAI_KEY })
// Good: client calls edge function
const res = await fetch(`${SUPABASE_URL}/functions/v1/ai-chat`, {
method: 'POST',
headers: { Authorization: `Bearer ${session.access_token}` },
body: JSON.stringify({ messages }),
})
EXPO_PUBLIC_* bakes into the bundle. Anyone can strings your .ipa and pull it out. This is not theoretical — it happens weekly to indie apps.
Lesson 3 — One data contract, all templates
Every template's marketing page on our site renders from the same TypeScript interface:
export interface ProductData {
slug: string
name: string
tagline: string
screenshots: Screenshot[]
pricing: PricingTier[]
features: Feature[]
testimonials: Testimonial[]
faqs: FAQ[]
techStack: TechItem[]
}
Sixty-three lines. Every template's config satisfies it. Every marketing component consumes it. Zero forks.
Before this contract, we had five copies of the pricing card. A fix on one wasn't a fix on all. After the contract: one component, one bug fix, seven templates updated.
Lesson 4 — Ship CLAUDE.md, AGENTS.md, .cursorrules
Every template ships with agent-readiness files. Short, opinionated, points to lib/data/types.ts and lists invariants.
Result: support tickets about "the agent broke something in your template" dropped noticeably. Not because agents got smarter — because they had a map.
Structure that works:
# CLAUDE.md
## Invariants
- Do not touch `lib/data/types.ts` — it's the shared contract
- Every new screen goes in `app/(tabs)/`, wired via Expo Router
- Edge functions in `supabase/functions/`, never in the client
## File map
- app/apps/config/*.config.ts — template configs
- app/apps/components/ — shared marketing UI
- app/apps/lib/data/ — data layer
## When adding a feature
1. Update the config type first
2. Then update the config value
3. Then update the UI that reads it
Lesson 5 — Keep the entry price low
Templates range $49–$99. Temptation: raise everything to $99. Support-per-dollar is worse at $49.
Reality: $49 buyers come back for $79–$99 templates. The cheap template is a paid trial. Kill it and the funnel collapses.
Lesson 6 — Docs convert, not screenshots
Analytics: converting buyers spend more time in /docs than on the marketing page. Specifically the "how do I swap the AI provider" sections.
Writing docs first also forces you to design the escape hatch before the feature. If you can't write the "how do I swap this" section, the feature isn't done.
Lesson 7 — Stack review
Would pick again:
- Expo SDK 54, RN 0.81 — new arch stable
- NativeWind v4 — kills styling drift across templates
- Supabase — auth, Postgres, edge, storage, one bill
- TanStack Query v5 — replaced Redux everywhere
- Expo Router v6 — file-based routing
Would skip:
- Zustand for state that TanStack Query handles. Two templates had Zustand stores that were purely query-cache reinventions. Removing them cost more than not adding them.
For year two
- Config file exists before any RN code does
- Docs written before the feature
- CLAUDE.md written during design
Top comments (0)