TL;DR
- Claude Code is a tool, not a mobile app strategy — the strategy is the substrate it runs on.
- The parts that break aren't code generation: EAS Build credentials, Supabase RLS contracts, Stripe webhook correctness, and AI keys leaking through
EXPO_PUBLIC_*. - An agent can implement any of these correctly — it just won't know it should until the pattern already exists in the repo.
- Give Claude Code a well-shaped repo (
CLAUDE.md, visible patterns, typed boundaries) and it's 10x faster. Give it a blank folder and it's a liability with autocomplete.
Every React Native thread on Reddit right now has some version of the same take: "why would I buy a template when Claude Code exists?"
Fair question. Wrong framing.
Claude Code is a tool. A mobile app strategy is a substrate. Confusing the two is why so many AI-generated Expo apps die in the TestFlight queue.
Let me be specific about the failure modes.
Where Claude Code alone breaks on mobile
EAS Build is stateful
eas build --platform ios --profile production
That command needs:
- Apple ID + team ID
- Distribution certificate
- Provisioning profile matching the bundle ID
- App Store Connect API key
- A working
eas.jsonwith the rightproductionprofile - Metro bundler config that plays nice with your assets
None of this is code. It's account state and CLI ritual. Claude Code can generate a plausible-looking eas.json, but it can't create your certificates, can't upload them to Apple, and can't recover when your provisioning profile expires mid-build.
Supabase RLS is a contract, not a vibe
Ask an agent to "add auth" and you'll often see:
create policy "allow all" on public.users
for all using (true);
That's not a policy. That's a data breach with a create policy statement in front of it.
Real RLS looks like:
create policy "users read own row"
on public.users for select
using (auth.uid() = id);
create policy "users update own row"
on public.users for update
using (auth.uid() = id)
with check (auth.uid() = id);
And that's just the trivial case. Multi-tenant apps, invitation flows, sharing, admin roles — all require policy design before the agent starts writing. The agent can implement your contract. It shouldn't invent it. Docs: Supabase RLS reference.
Payments do not tolerate hallucination
A Stripe webhook handler generated by an agent:
export async function POST(req: Request) {
const body = await req.json();
if (body.type === 'checkout.session.completed') {
await grantLicense(body.data.object.customer_email);
}
return new Response('ok');
}
Everything is wrong. req.json() breaks signature verification (you need the raw body). There's no idempotency check. Email is a bad key for entitlement. There's no error handling. And where's the signing secret validated?
Correct version — abbreviated:
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function POST(req: Request) {
const sig = req.headers.get('stripe-signature')!;
const raw = await req.text();
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(
raw,
sig,
process.env.STRIPE_WEBHOOK_SECRET!,
);
} catch {
return new Response('bad signature', { status: 400 });
}
if (event.type === 'checkout.session.completed') {
const session = event.data.object as Stripe.Checkout.Session;
await upsertLicense({
stripeCustomerId: session.customer as string,
productId: session.metadata!.product_id,
idempotencyKey: event.id,
});
}
return new Response('ok');
}
The point is not that Claude Code couldn't write the correct version. It's that it doesn't know it should until you tell it. That's the strategy — deciding what "correct" looks like before the prompt.
AI provider keys leak by default
If your Expo app imports:
import OpenAI from 'openai';
const openai = new OpenAI({
apiKey: process.env.EXPO_PUBLIC_OPENAI_KEY,
});
You just shipped your OpenAI key to every user of your app. EXPO_PUBLIC_* env vars are bundled into the JS. Anyone can curl your bundle and read it.
The strategy answer: every LLM call goes through a Supabase edge function that holds the key.
// Client
const res = await fetch(`${supabase.functions.url}/summarize`, {
method: 'POST',
headers: { Authorization: `Bearer ${session.access_token}` },
body: JSON.stringify({ text }),
});
// Edge function
Deno.serve(async (req) => {
const { text } = await req.json();
const client = new OpenAI({ apiKey: Deno.env.get('OPENAI_KEY')! });
const stream = await client.chat.completions.create({
model: 'gpt-4o-mini',
stream: true,
messages: [{ role: 'user', content: text }],
});
return new Response(stream.toReadableStream());
});
Claude Code can write both halves. It won't know to write the second half unless the pattern is already in the repo.
Where Claude Code is genuinely great
Given a well-shaped repo, Claude Code is transformative. Some concrete wins:
-
Screen scaffolding. Point it at
app/(tabs)/index.tsxand ask for a variant. It preserves imports, styling, and data-fetching patterns. - Migration writing. Give it two existing Supabase migrations. Ask for a third. It matches your style, including RLS.
- Edge function templating. Given one AI provider, it can generate an equivalent for another.
-
Codebase Q&A. "Where does the license grant happen?" — with
file:lineaccuracy.
The requirement: a repo where those patterns are visible. That's what a template gives you.
Comparison
| Concern | Just Claude Code | Claude Code + Substrate |
|---|---|---|
| Time to first TestFlight | Days–weeks | Same day |
| RLS + auth | Invented per session | Pre-designed, tested |
| EAS Build config | You figure it out |
eas.json profiles included |
| Payments | Hallucinated handlers | Verified webhook + entitlements RLS |
| Design | Mean-of-training-data | Brand-tokenized |
| Agent conventions | Reinvented every prompt |
CLAUDE.md, slash commands |
| Long-term maintenance | Code drift | Documented architecture |
What "having a strategy" actually means
Decisions made before you prompt:
- Distribution — Expo + EAS, which profiles, which channels
- Backend — Supabase tables, RLS design, edge functions
- AI provider — model, streaming, cost cap, key location
- Payments — Stripe web / IAP / hybrid
- Design system — typography, tokens, dark mode, motion
- Agent conventions —
CLAUDE.md, slash commands - Store readiness — privacy manifest, subscription copy
You can build all of this yourself. It's 3–6 weeks of mid-level mobile engineering work per app. That's the honest baseline.
Or you start from a substrate that has it decided already — like the Applighter React Native + Expo templates, which ship with all of the above and a CLAUDE.md that turns Claude Code into a targeted execution engine rather than a random screen generator.
The one-line version
Claude Code is the leverage. A mobile app strategy is the fulcrum. You need both.
If you're building an AI feature, start with a substrate that ships the AI patterns already — AI Voice Notes and Chat with PDF are the two we recommend as starting points.
What are you building right now that hit one of these walls? Drop it in the comments — especially if it was the RLS one.
Top comments (0)