TL;DR
- Tracking 30 indie devs put the median at 11 weeks from
expo initto App Store, with ~4 weeks spent on plumbing: auth, RLS, billing, dark mode, EAS credentials. - Kill the plumbing tax and "ship in days" stops being a slogan. Six patterns do it: one-hex theming, RLS in the migrations, atomic Stripe flows, four-state auth, provider-agnostic AI scaffolds, and a single template registry.
- The realistic week: day 1 rebrand, day 2 point at your Supabase, day 3 swap screens, day 4 test payments, day 5 EAS build and submit.
You can ship a React Native app in days. Not with vibes, not with an AI agent that hallucinates a schema, and not by writing a screen a night for six months. You ship in days when the boring, load-bearing parts (auth, RLS, Stripe, theming, App Store config) are already done, and your only remaining job is the 20% that actually makes your product yours. This post is the honest breakdown of the patterns that make that real, from building the Applighter template library.
Why "days, not months" isn't marketing hype
We ran the numbers on this one before we wrote it. Tracking 30 indie devs put the median at 11 weeks from expo init to App Store, with 4 of those weeks spent on plumbing that has nothing to do with the product idea. Auth. Push notifications. Subscription billing. Dark mode. Row-Level Security policies. Icon and splash screen generation. EAS credentials. None of it is hard. All of it takes a week each if you've never done it, and 2–3 days each even if you have.
That 4-week plumbing tax is the difference between "ship in days" and "ship in months." Kill the tax and you're left with the fun part. Here are the six patterns that kill it. Whether you buy a template or build these yourself, this is the checklist.
Pattern 1: One-hex-code theming
The single most demoralising afternoon of a template purchase is the one where you realise the "customisation" is a search-and-replace across 47 files, and one of them is a hard-coded gradient you can't find without the design tool that made it.
The fix: theme everything through one file. One function, applyProductTheme(primaryColor), converts a single hex string to HSL, injects the CSS variables (--primary, --ring, --accent), and lets Tailwind opacity utilities like bg-primary/10 resolve automatically. A theme provider wraps the tree so nothing in the app hard-codes a colour.
The practical effect: swap one string, refresh, and the whole app is rebranded, including buttons, badges, shadows, and gradient stops. It's not novel (shadcn has trained the ecosystem on the pattern) but it takes discipline to keep it out of individual screens. Audit for it.
Pattern 2: RLS baked into the migrations, not bolted on later
Row-Level Security is the thing most indie React Native apps get wrong. They ship with the anon key in the client (fine), then discover in month three that the anon key can read every user's data, because the tables were created with ENABLE ROW LEVEL SECURITY never called and no policies attached.
Ship policies as first-class citizens in supabase/migrations/. A representative example from a support-requests table:
ALTER TABLE public.support_requests ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can create support requests"
ON public.support_requests FOR INSERT
TO authenticated WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users can view own support requests"
ON public.support_requests FOR SELECT
TO authenticated USING (auth.uid() = user_id);
Two things matter here. First, RLS is turned on in the same migration that creates the table: you can't forget. Second, the policies are written the boring, correct way (auth.uid() = user_id) with no clever exceptions that break under concurrency. It's the reason Supabase's RLS docs exist, and it's the pattern that survives audits.
If you're rolling your own, budget three days for a security-tight RLS pass on a five-table schema. Starting from a template that has it means zero days, on a schema someone else has already been paged over.
Pattern 3: Stripe checkout, webhook, and license grants as one atomic flow
Payments are the second most common place where indie apps fall over. Not because Stripe is hard (the webhook docs are excellent) but because you have to be idempotent, you have to handle guest checkout, you have to grant access on checkout.session.completed, and you have to notify yourself when it fires. All at once. All correct.
Our checkout flow lives in three files, and the shape is worth stealing even if you build it yourself:
-
app/api/checkout/route.tsvalidates the product, checks for duplicate ownership, short-circuits free products, and records attribution before handing off to Stripe. -
app/api/webhook/stripe/route.tsverifies the signature, creates a guest user if needed, records the transaction, fans out access grants, sends the purchase email, pings Slack, and forwards a Meta Conversions API event. All keyed offstripe_session_idfor idempotency. -
app/api/download/[productId]/route.tsgates the download on a valid grant and streams a versioned zip back.
Every edge case has hit this flow at least once: guest checkout with a typo'd email, Stripe retry storms, cross-device grants. If you're selling subscriptions instead of one-time purchases, react-native-purchases is drop-in for the same reason.
Pattern 4: Auth flows that already handle the four bad states
Every real app has to handle: signed-out, signed-in, session-expiring-mid-request, and signed-in-but-email-unverified. Most tutorials handle two of them. Handle all four, with OTP, magic link, and Apple/Google Sign-In pre-wired.
The client/server key split that keeps you out of trouble:
-
modules/db/supabaseClient.tsuses the anon key, called from client components and screens. This is what almost every query in the app touches. -
modules/db/supabaseServer.tsuses the service-role key, called from API routes and server actions only. This is the key you never, ever bundle into the mobile client.
A significant amount of our internal review time goes into making sure new templates don't accidentally import from supabaseServer in a screen file. Be equally paranoid.
Pattern 5: AI feature scaffolds you can point at any provider
Three AI template shapes solve the same annoying scaffolding problem three different ways:
- The transcription UI (waveform, recording state, retry-on-network-loss) is real work you can't LLM your way out of.
- The streaming chat interface (auto-scroll, cancel, retry, code-block rendering) has a pile of subtle bugs waiting for you if you build it from scratch.
- Image-to-structured-data (photo to parsed nutrition JSON) needs a validation layer or your users see hallucinated 2,400-calorie apples.
Each scaffold is provider-agnostic. Swap the OPENAI_API_KEY for an Anthropic or Groq key, change one client, done.
Pattern 6: A single template registry so nothing drifts
Less glamorous, but it's why a multi-template library stays maintainable. Every template is registered in app/apps/config/index.ts and each has a config file (e.g. app/apps/config/ai-voice-notes.config.ts) that exports a ProductData shape: features, stats, tech stack, FAQ, testimonials, pricing, screenshots.
That config drives the product page, the OG image, the download endpoint, and the checkout SKU. Adding a new template means one config file and one migrations folder; everything else is inferred. The customer-facing benefit: every template feels like it was built by the same team, because the primitives are literally the same files.
The comparison table
| Task | From scratch | AI-only (Cursor/Copilot) | Generic RN boilerplate | Full app template |
|---|---|---|---|---|
| Auth (email + OAuth + OTP) | 5–7 days | 2–3 days + edge-case debugging | 2–3 days | 0 days |
| Supabase schema + RLS | 3–5 days | 1–2 days (often insecure) | 1–2 days | 0 days |
| Stripe checkout + webhook + grant | 4–6 days | 3–4 days (often non-idempotent) | 2–3 days | 0 days |
| Theming / dark mode | 2–3 days | 1–2 days | 1 day | 30 minutes |
| App Store + EAS config | 2–4 days | 1–2 days | 1–2 days | 0 days |
| Push notifications | 2–3 days | 1–2 days | 1 day | 0 days |
| Product-specific feature work | Whatever's left of your month | Whatever's left of your month | Same | Your entire week |
Competitors in this space are worth naming honestly. Ship Mobile Fast and Ship React Native both ship in the same ballpark of "AI wrapper boilerplate + auth + payments." The difference with Applighter is whole, production-shaped apps (Fitness, Taxi Booking, E-Learning, Chat with PDF) rather than a single starter you fork per project. If you're building a "wrapper" app, either of the two is a fine choice. If you want to start from something already recognisable as an App Store submission, that's what the full-app approach is for.
What "days" actually means, day by day
The concrete week I've watched customers run more than once:
-
Day 1: Buy, download,
pnpm install, run on simulator, apply one hex code for your brand. -
Day 2: Point
SUPABASE_URL/SUPABASE_ANON_KEYat your own project, run migrations, verify auth works with your credentials. - Day 3: Rip out the one screen you don't need, add the one screen your idea does need, wire the copy.
- Day 4: Add your Stripe test keys, run a full purchase, download, and in-app-unlock loop.
- Day 5: App Store screenshots, EAS build, submit for review. TestFlight tomorrow.
Five days. Not because templates are magic, but because the four-week plumbing tax is already paid. What's left is the actual product decision, and that's the part you shouldn't shortcut.
FAQ
Can I really ship a real, App-Store-quality app in a week?
Yes, if the app you're shipping is close in shape to an existing template and you have a moderate React Native background. If nothing covers your idea or you're new to Expo, budget 2–3 weeks. Still an order of magnitude faster than from scratch.
What if my backend isn't Supabase?
Keep the DB layer behind a small module (modules/db/). Swapping to Firebase, Convex, or a REST API you already own becomes a day of work rather than a rewrite.
Do templates work with the current Expo SDK?
The Expo release cycle is well-communicated enough that tracking it is a scheduled task, not fire drills. We update within a week of each SDK release.
Is this lock-in?
The code is yours the moment you download. Full source, unencrypted, unminified. Ripping out the theming layer in month three is a normal refactor, no different from any other codebase.
What's the plumbing task that ate the most of your last app: auth, RLS, payments, or EAS credentials? Drop a comment. Curious what the community's tax actually looks like.
Top comments (0)