Seven React Native templates in twelve months. Two AI-first, five conventional apps with AI stitched into the seams.
This is the concrete retrospective: the stack, the schema, the payment webhooks, the SDK bumps, the pricing. If you're building a template business or considering buying one, it's the post I wanted a year ago.
- One AI job per template beats a chat tab bolted onto a generic app
- The schema with correct RLS is the hard part; screens are replaceable
- SDK upgrades are the largest recurring cost and we didn't price them in
- Payments took four weeks, not four days
- Cheap templates sold fewer copies, not more
The stack
After two false starts we locked it:
Runtime : Expo SDK 54, new architecture
Language : TypeScript, strict
UI : NativeWind
Motion : Reanimated + Gesture Handler
State : Zustand + TanStack Query
Backend : Supabase (Postgres, Auth, Storage, Edge Functions)
Payments : Stripe, webhooks handled server-side
LLM : Provider abstraction so the model is swappable
Tooling : EAS build, update, submit
Each piece was picked for one property: it survives contact with a customer who doesn't know the library. NativeWind classes are searchable. Worklets don't stall when the JS thread does. Supabase migrations are versioned SQL you can read.
The registry pattern
Every template is a config file registered in one map:
// app/apps/config/index.ts
export const appConfigs: Record<string, ProductData> = {
"weather-app": weatherAppConfig,
"fitness-app": fitnessAppConfig,
"e-learning-app": eLearningAppConfig,
"taxi-booking-app": taxiBookingAppConfig,
"ai-voice-notes": aiVoiceNotesConfig,
"chat-with-pdf": chatWithPdfConfig,
};
Adding a template is one import and one map entry. A database-backed catalog was tempting for about a week and turned out to be unnecessary. Seven templates fit in a Record comfortably.
1. AI feature, not AI wrapper
Both of our best-selling AI templates do exactly one AI thing.
// AI Voice Notes: one job, audio to structured summary
async function summarize(audioUri: string) {
const transcript = await transcribe(audioUri);
return await extractStructure(transcript); // { summary, actions, decisions }
}
No chat bubble. No "ask this app anything" tab.
The templates where we bolted a chat tab onto a generic screen underperformed the ones where the AI was the feature. A chat tab is a way of not deciding what the AI is for, and buyers can tell.
2. RLS is the moat
Screens are easy to replace. A schema with correct row-level security takes weeks.
Every template ships migrations under supabase/migrations/, and every read and write is gated by a policy assuming auth.uid().
create policy "read own notes" on notes
for select using (auth.uid() = user_id);
create policy "write own notes" on notes
for insert with check (auth.uid() = user_id);
create policy "update own notes" on notes
for update using (auth.uid() = user_id);
The bug we caught most in review was a missing sibling policy. Select correct, update absent. It fails silently, because the read works and the write just quietly does nothing to rows it shouldn't touch.
We test every template with two dummy accounts on a fresh Supabase project. Not optional. Reference: Supabase RLS docs.
3. SDK bumps eat weeks
Three SDK versions across the year. Each bump cost roughly a full engineering week per template:
- New architecture opt-in
-
expo-audioreplacingexpo-av - Native modules re-verified on both platforms
- Animation API drift
Seven templates, three bumps, a week each. That arithmetic is unpleasant and we didn't price it into the first three templates. We paid for it in December.
If you sell templates, maintenance is not overhead. It's the product.
4. Payments is a project
The webhook granting a customer access to what they bought is larger than any screen in any template.
type Grant = { userId: string; productId: string; source: "single" | "bundle" };
async function reconcile(event: Stripe.Event) {
switch (event.type) {
case "checkout.session.completed": /* create grant(s) */ break;
case "charge.refunded": /* revoke grant */ break;
case "customer.updated": /* rekey email */ break;
// multi-line-item bundles, dedupe against prior grants,
// same customer arriving with two different emails
}
}
Plan four weeks. Not four days.
The cases that broke us were never the happy path. They were bundles containing a template the customer already owned, and refunds arriving after an email change.
5. NativeWind and Reanimated
A boring combination that kept winning:
import Animated, { FadeIn } from "react-native-reanimated";
export function Card({ title }: { title: string }) {
return (
<Animated.View entering={FadeIn} className="p-4 rounded-2xl bg-white dark:bg-slate-900 shadow">
<Text className="text-lg font-semibold text-slate-900 dark:text-white">{title}</Text>
</Animated.View>
);
}
Searchable classes, worklets on the UI thread, and an unexpected third benefit: AI assistants get both libraries right on the first attempt.
That last one matters more than we expected, because most of our customers open the template in an AI editor rather than reading it top to bottom. A stack that models well is a stack that gets extended correctly.
6. Demo video beats README
Every template config carries a video. A short edited walkthrough moved conversion more than any pricing test we ran.
Not a screen recording. An edit.
7. Pricing
Single-app licence at $79, with a bundle at a materially better per-app rate. Two things surprised us.
Bundle attach rate is high. Customers who buy one template come back for the bundle rather than buying a second single.
And cheaper templates sold fewer copies, not more. Below a certain price, buyers appear to read the price as a signal about quality, and the ones who don't are not the customers you want anyway.
Scratch versus template
For a buyer starting a new app:
| Task | From scratch | With a template |
|---|---|---|
| Auth (email, OAuth, Apple) | About a week | Included |
| Supabase schema and RLS | About a week | Included |
| Push notifications | Several days | Included, minus certificates |
| AI feature with streaming and storage | About a week | Included |
| Design tokens | A few days | Included |
| Icon, splash, EAS config | A couple of days | Included |
| Your own payments, if you sell something | About a week | Still yours |
That last row is the honest one. A template gets you to a working app quickly. It does not remove the work of charging your own customers, and any comparison table that says otherwise is selling you something.
What we'd do differently
- Pick the styling stack on day one
- Write the schema before the screens
- Record the demo video before launch, not after
- One AI job per template, never a chatbot
- Test on a clean machine before every release
- Price maintenance from the start
Wrap
A year, seven templates, more webhook edge cases than anyone deserves.
The ones that survived contact with customers had real schemas, one focused AI job, and payments that didn't fall over on the first refund. Everything else was decoration, and we learned which was which by shipping the decoration first.
Full write-up with the internal architecture detail is on the Applighter blog.
If you sell templates or plugins, what did maintenance actually cost you in year one? I suspect most people underprice it the same way we did, and nobody publishes the number.
Top comments (0)