<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Russel Dsouza</title>
    <description>The latest articles on DEV Community by Russel Dsouza (@russel_dsouza_bd584a3cb2a).</description>
    <link>https://dev.to/russel_dsouza_bd584a3cb2a</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3939420%2F1d36f555-5b2b-48e8-a97c-2acbb7603dbd.png</url>
      <title>DEV Community: Russel Dsouza</title>
      <link>https://dev.to/russel_dsouza_bd584a3cb2a</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/russel_dsouza_bd584a3cb2a"/>
    <language>en</language>
    <item>
      <title>How to Publish Your Lovable App to the App Store (Without Rebuilding It Yourself)</title>
      <dc:creator>Russel Dsouza</dc:creator>
      <pubDate>Wed, 22 Jul 2026 09:43:09 +0000</pubDate>
      <link>https://dev.to/russel_dsouza_bd584a3cb2a/how-to-publish-your-lovable-app-to-the-app-store-without-rebuilding-it-yourself-2jia</link>
      <guid>https://dev.to/russel_dsouza_bd584a3cb2a/how-to-publish-your-lovable-app-to-the-app-store-without-rebuilding-it-yourself-2jia</guid>
      <description>&lt;ul&gt;
&lt;li&gt;Lovable builds a &lt;strong&gt;web app&lt;/strong&gt;. The App Store distributes &lt;strong&gt;native&lt;/strong&gt; apps. That's a category gap, not a skill gap.&lt;/li&gt;
&lt;li&gt;WebView wrappers feel wrong (scroll momentum, transitions, haptics, back gesture) and risk rejection under &lt;strong&gt;App Store Guideline 4.2&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The real fix is a React Native + Expo rebuild — plus certificates, provisioning profiles, signing keys, screenshots, and metadata.&lt;/li&gt;
&lt;li&gt;If your Lovable app uses Supabase, a native rebuild can point at the same project. No migration needed.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;You built something in Lovable over a weekend. It works. You sent the link to friends and they said "wait, you built this?"&lt;/p&gt;

&lt;p&gt;Then someone asked: &lt;em&gt;"Is it on the App Store?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That's where most Lovable projects stall.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why you can't submit a Lovable app to the App Store directly
&lt;/h2&gt;

&lt;p&gt;Building an app and shipping an app are two different projects.&lt;/p&gt;

&lt;p&gt;Lovable is excellent at the first one. But the App Store distributes native apps, and Lovable produces a web app. That's not a skill gap — it's a category gap.&lt;/p&gt;

&lt;p&gt;To publish, you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An Apple Developer account ($99/year)&lt;/li&gt;
&lt;li&gt;A bundle identifier&lt;/li&gt;
&lt;li&gt;A distribution certificate and provisioning profile&lt;/li&gt;
&lt;li&gt;A signed &lt;code&gt;.ipa&lt;/code&gt; binary&lt;/li&gt;
&lt;li&gt;Screenshots at every required device size&lt;/li&gt;
&lt;li&gt;A privacy nutrition label and data-usage disclosure&lt;/li&gt;
&lt;li&gt;A human reviewer who might say no&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of that comes out of a web app builder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why wrapping it in a WebView doesn't work
&lt;/h2&gt;

&lt;p&gt;The obvious shortcut is a wrapper: put the web app in a native shell and ship the shell. Two problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It feels wrong.&lt;/strong&gt; Scroll momentum is off. Transitions don't match the platform. Text inputs behave like a browser instead of a native keyboard. No haptics. The back gesture doesn't do what a thumb expects. Users can't name it, but they register it as "website in a box."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Apple notices too.&lt;/strong&gt; Guideline 4.2 exists specifically for repackaged websites that don't provide enough native value. Plenty of wrappers slip through, but you're rolling dice on review — and each rejection costs a full cycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a real conversion actually involves
&lt;/h2&gt;

&lt;p&gt;The honest answer is a native rebuild: React Native + Expo, real navigation, real gestures, real device APIs (camera, push, the rest).&lt;/p&gt;

&lt;p&gt;Technically that's the right call. Practically it's where projects die — you spent two days building the product and you're now looking at learning the entire Apple release toolchain, or paying someone who already has.&lt;/p&gt;

&lt;p&gt;That mismatch is the whole problem. The building was fun and fast. The shipping is neither.&lt;/p&gt;

&lt;p&gt;If you'd rather hand that part off, &lt;a href="https://www.rapidnative.com/lovable-to-app-store?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=lovable-to-app-store" rel="noopener noreferrer"&gt;RapidNative converts Lovable apps to React Native and handles the full submission&lt;/a&gt; — signing, screenshots, metadata, the lot. You get a preview build on your device first, the full Expo source code afterward (no lock-in), and rejections are handled at no extra cost until you're approved. Typical turnaround is 1–2 weeks.&lt;/p&gt;

&lt;p&gt;Worth noting for anyone doing this themselves too: &lt;strong&gt;if your Lovable app already talks to Supabase, you don't need to migrate anything.&lt;/strong&gt; Point the React Native app at the same project and your data layer is done.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;Building and shipping are separate problems. Being good at one tells you nothing about how hard the other will be.&lt;/p&gt;

&lt;p&gt;Lovable made building dramatically easier. Apple's requirements haven't moved — and Google added a 12-tester closed-testing gate on top.&lt;/p&gt;

&lt;p&gt;If your app is stuck one wall away from the store, that's not a verdict on your product. It's just a different category of work than the one you finished.&lt;/p&gt;




&lt;p&gt;Been through App Store submission yourself? Which screen broke you? Mine was the privacy nutrition label.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>nocode</category>
      <category>ios</category>
      <category>mobile</category>
    </item>
    <item>
      <title>5 Places Sensitive Data Leaks in a React Native App (and How to Plug Them)</title>
      <dc:creator>Russel Dsouza</dc:creator>
      <pubDate>Tue, 21 Jul 2026 07:49:01 +0000</pubDate>
      <link>https://dev.to/rapidnative-ai/5-places-sensitive-data-leaks-in-a-react-native-app-and-how-to-plug-them-3gf1</link>
      <guid>https://dev.to/rapidnative-ai/5-places-sensitive-data-leaks-in-a-react-native-app-and-how-to-plug-them-3gf1</guid>
      <description>&lt;ul&gt;
&lt;li&gt;The leaks that matter usually aren't in production code — they're in the workflow around it.&lt;/li&gt;
&lt;li&gt;Five common ones: &lt;strong&gt;AsyncStorage for tokens&lt;/strong&gt;, &lt;strong&gt;real user data in design mockups&lt;/strong&gt;, &lt;strong&gt;multi-stage AI pipelines&lt;/strong&gt;, &lt;strong&gt;logs and crash reports&lt;/strong&gt;, and &lt;strong&gt;bundled permissions&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Two greps that find real bugs today: &lt;code&gt;AsyncStorage.setItem&lt;/code&gt; near &lt;code&gt;token&lt;/code&gt;, and &lt;code&gt;console.log(user&lt;/code&gt; / &lt;code&gt;console.log(token&lt;/code&gt; / &lt;code&gt;console.log(prompt&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Model consent by &lt;em&gt;purpose&lt;/em&gt;, not by OS permission. Camera consent is not AI-summarization consent.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Data protection posts usually focus on the shipped binary. Encrypt this, pin that certificate, wrap the API client. All correct, all necessary — and all too late if the leak happened three weeks earlier in a design mockup.&lt;/p&gt;

&lt;p&gt;We build AI-generated React Native apps at RapidNative, and the interesting security bugs almost never live in production code. They live in the space between code, design, and AI workflows. Here are five leak points we've watched teams miss.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. AsyncStorage for anything sensitive
&lt;/h2&gt;

&lt;p&gt;The classic. &lt;code&gt;AsyncStorage&lt;/code&gt; is convenient, unencrypted key-value storage. A user session token in AsyncStorage is one rooted device or one iCloud backup extraction away from being replayed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; use &lt;code&gt;expo-secure-store&lt;/code&gt; (Keychain on iOS, Keystore on Android) for anything that unlocks an account.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;SecureStore&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;expo-secure-store&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;saveSessionToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SecureStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItemAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;session_token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your codebase greps positive for &lt;code&gt;AsyncStorage.setItem&lt;/code&gt; and &lt;code&gt;token&lt;/code&gt;, that's your first PR.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Real user data in design prototypes
&lt;/h2&gt;

&lt;p&gt;A designer opens Figma, needs a realistic list, and pastes 20 rows from the production customer export. Now customer names sit in the design file, which sits in a Figma team folder, which was shared to a contractor's personal email nine months ago.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; a synthetic data script committed to the repo. &lt;code&gt;npx generate-mocks users 20&lt;/code&gt; should be faster than exporting production data. Make the fast path the safe path.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Multi-stage AI workflows
&lt;/h2&gt;

&lt;p&gt;Voice memo lands. Transcription service returns text. A second model extracts tasks. Teammates get pinged.&lt;/p&gt;

&lt;p&gt;That's four handoffs, and the security review probably only covers the API endpoint at step 1.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; draw the pipeline as boxes, list what data each box sees, and confirm each hop has access controls tied to the user's actual consent — not a bundle. Camera consent is not AI-summarization consent.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Logs and crash reports
&lt;/h2&gt;

&lt;p&gt;Sentry, Bugsnag, Datadog, or whatever log aggregator you use sees everything the app hands it. Session tokens in &lt;code&gt;Authorization&lt;/code&gt; headers. AI prompt bodies with user text. Full user IDs in breadcrumbs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; scrub before you send. Most SDKs support a &lt;code&gt;beforeSend&lt;/code&gt; hook.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Sentry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nf"&gt;beforeSend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Grep your codebase for &lt;code&gt;console.log(user&lt;/code&gt;, &lt;code&gt;console.log(token&lt;/code&gt;, and &lt;code&gt;console.log(prompt&lt;/code&gt;. That's your second PR.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Bundled permissions
&lt;/h2&gt;

&lt;p&gt;You ask for camera on onboarding: "we need this for document capture." The user agrees. Six months later a new AI-summarization feature ships that also uses camera frames.&lt;/p&gt;

&lt;p&gt;Same permission, different purpose. Legally shaky (GDPR Article 25 asks for data minimization by design), and practically a betrayal of what the user thought they said yes to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; model consent as a first-class type, keyed on purpose rather than on OS permission:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ConsentEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;granted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;withdrawn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;policyVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;purpose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;document-capture&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ai-summary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;marketing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;surface&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;onboarding&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;settings&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;feature-gate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ship a new feature, ask again for that purpose. A one-line inconvenience for the user beats the €7.1B in GDPR fines the EU has issued as of January 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern
&lt;/h2&gt;

&lt;p&gt;Notice what the five have in common: none of them are broken TLS, weak crypto, or a mangled JWT signature. They're all &lt;strong&gt;workflow leaks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Encryption and pinning are table stakes. The bugs that leak your customer's data in 2026 are the ones nobody put in the security review — because they happen in Figma, in a Slack DM, in a Sentry payload, or in an AI pipeline diagram that was never drawn.&lt;/p&gt;

&lt;p&gt;Draw the diagram. Grep the logs. Model consent. Ship.&lt;/p&gt;

&lt;p&gt;Full source with the OWASP and NIST references: &lt;a href="https://rapidnative.com/blogs/data-protection?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=data-protection" rel="noopener noreferrer"&gt;rapidnative.com/blogs/data-protection&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Which of the five would your codebase fail right now? Mine failed #4 the first time I checked.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>security</category>
      <category>mobile</category>
      <category>webdev</category>
    </item>
    <item>
      <title>You Don't Need 10 Refactoring Tools. Pick 2 From This Decision Tree.</title>
      <dc:creator>Russel Dsouza</dc:creator>
      <pubDate>Tue, 21 Jul 2026 07:35:58 +0000</pubDate>
      <link>https://dev.to/rapidnative-ai/you-dont-need-10-refactoring-tools-pick-2-from-this-decision-tree-2cho</link>
      <guid>https://dev.to/rapidnative-ai/you-dont-need-10-refactoring-tools-pick-2-from-this-decision-tree-2cho</guid>
      <description>&lt;ul&gt;
&lt;li&gt;Answer three questions — &lt;strong&gt;scope of change&lt;/strong&gt;, &lt;strong&gt;how type-heavy your codebase is&lt;/strong&gt;, and &lt;strong&gt;who runs it&lt;/strong&gt; — and the tool choice collapses to two.&lt;/li&gt;
&lt;li&gt;Solo TS-heavy repo: &lt;strong&gt;VS Code + &lt;code&gt;ts-morph&lt;/code&gt;&lt;/strong&gt;. Small team, one repo: &lt;strong&gt;WebStorm + &lt;code&gt;jscodeshift&lt;/code&gt;&lt;/strong&gt;. Multi-repo org: &lt;strong&gt;&lt;code&gt;ast-grep&lt;/code&gt; + Sourcegraph Batch Changes&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;You can skip Babel, Comby, OpenRewrite/Moderne, and ReSharper unless you're in their specific ecosystem.&lt;/li&gt;
&lt;li&gt;Tool sprawl is a symptom: if you're debating 10 tools, the real gap is that nobody's defined what's worth automating.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Every "best refactoring tools" post in 2026 gives you the same 10 names and lets you sort them out.&lt;/p&gt;

&lt;p&gt;But 51% of developers now use AI coding tools daily, and the refactoring-tools market is on track from $1.74B to $5.82B by 2033. That growth means more tools, not clearer choices.&lt;/p&gt;

&lt;p&gt;Here's a smaller frame. Answer three questions, pick two tools, ignore the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 3 questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Scope of the change?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One file, minutes of work: your IDE&lt;/li&gt;
&lt;li&gt;Same rewrite across many files: a codemod&lt;/li&gt;
&lt;li&gt;Same rewrite across many repos: a batch orchestrator&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How type-heavy is your codebase?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Loose JS or mixed languages: AST / pattern tools&lt;/li&gt;
&lt;li&gt;Strict TypeScript: type-aware transforms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Who runs it?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Solo: CLI is fine&lt;/li&gt;
&lt;li&gt;Team: you need a shared recipe and review flow&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The 2-tool stacks
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Solo dev, TypeScript-heavy React Native repo.&lt;/strong&gt; Pick VS Code (rename, quick fixes, everyday cleanup) and &lt;code&gt;ts-morph&lt;/code&gt; (type-aware programmable rewrites for the times VS Code's rename gives up). Two tools, both free, both scriptable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Small team, one repo, repetitive structural changes.&lt;/strong&gt; Pick WebStorm (symbol-aware preview so nobody merges a broken rename) and &lt;code&gt;jscodeshift&lt;/code&gt; (AST codemods for the "convert 200 class components to hooks" problem). WebStorm covers day-to-day, jscodeshift covers migrations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-repo org, needs consistency.&lt;/strong&gt; Pick &lt;code&gt;ast-grep&lt;/code&gt; (structural search-and-replace fast enough to run in CI) and Sourcegraph Batch Changes (turns a codemod into PRs across every repo). One writes the change, the other ships it everywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you can skip
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Babel&lt;/strong&gt;, if you're not building a plugin. It's a compiler with a refactoring side-quest, not a refactoring tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comby&lt;/strong&gt;, if your repo is mostly one language. Its edge is cross-language pattern matching.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenRewrite and Moderne&lt;/strong&gt;, if you're not on the JVM. Excellent recipes, wrong ecosystem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JetBrains ReSharper&lt;/strong&gt;, if you're not shipping .NET backends. If you are, you already own it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The real lesson
&lt;/h2&gt;

&lt;p&gt;Tool sprawl is a symptom.&lt;/p&gt;

&lt;p&gt;If your team is debating which of 10 refactoring tools to standardize on, the actual problem is that nobody has defined what kinds of changes are worth automating.&lt;/p&gt;

&lt;p&gt;Pick a scope, pick a scale, pick two tools. Ship a codemod. Learn what breaks. That's the loop.&lt;/p&gt;




&lt;p&gt;If you want the full 10-tool breakdown with when-to-use-each, we wrote it up &lt;a href="https://rapidnative.com/blogs/code-refactoring-tools?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=refactoring-tools-decision-tree" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What's your two-tool stack? Drop it in the comments — curious what people actually settle on.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Prompt-to-App Is a Lie — Here's What Actually Happens</title>
      <dc:creator>Russel Dsouza</dc:creator>
      <pubDate>Wed, 15 Jul 2026 13:02:07 +0000</pubDate>
      <link>https://dev.to/russel_dsouza_bd584a3cb2a/prompt-to-app-is-a-lie-heres-what-actually-happens-3fo5</link>
      <guid>https://dev.to/russel_dsouza_bd584a3cb2a/prompt-to-app-is-a-lie-heres-what-actually-happens-3fo5</guid>
      <description>&lt;p&gt;You type a sentence. Twenty seconds later there's a "React Native app." That's the pitch every prompt-to-app tool ships with in 2026 — Lovable, Bolt, v0, Replit Agent, and the rest. This post is about what actually happens after the demo screen.&lt;/p&gt;

&lt;p&gt;Prompt-to-app tools generate a live-reloading UI in a sandbox against a mocked backend. They do not generate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A real auth system with sessions, password reset, and OAuth&lt;/li&gt;
&lt;li&gt;A database you own with migrations and RLS&lt;/li&gt;
&lt;li&gt;Stripe wiring with real webhooks and Apple IAP handling&lt;/li&gt;
&lt;li&gt;An &lt;code&gt;.ipa&lt;/code&gt; or &lt;code&gt;.aab&lt;/code&gt; you can submit to a store&lt;/li&gt;
&lt;li&gt;The Apple Developer certificate needed to ship&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which is roughly 90% of the actual work.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the demo shows vs. what production needs
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Demo&lt;/th&gt;
&lt;th&gt;Production&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;UI in a web preview&lt;/td&gt;
&lt;td&gt;Signed &lt;code&gt;.ipa&lt;/code&gt; + &lt;code&gt;.aab&lt;/code&gt; submitted to two stores&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mocked API + sample data&lt;/td&gt;
&lt;td&gt;Postgres with migrations, backups, RLS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One screen&lt;/td&gt;
&lt;td&gt;Deep linking, push, background tasks, offline sync&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Preview URL&lt;/td&gt;
&lt;td&gt;Real accounts, OAuth, password reset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No integrations&lt;/td&gt;
&lt;td&gt;Stripe, IAP, RevenueCat, Sentry, analytics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No secrets&lt;/td&gt;
&lt;td&gt;Secret storage, key rotation, review notes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The three walls
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Auth.&lt;/strong&gt; The "Sign In" button works in the preview. It doesn't do anything real. Wiring actual OAuth + sessions + RLS is a rewrite of the data layer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Payments.&lt;/strong&gt; On iOS, selling anything digital requires Apple IAP, not Stripe. The tools don't know or care. You'll write this yourself, and it's a two-week job when you factor in webhook handling and license grants.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Submission.&lt;/strong&gt; No agent creates an Apple Developer certificate. No agent captures screenshots at five device sizes. No agent replies to reviewer feedback. This is 30% of any app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the demo works and the app doesn't
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Architecture drift.&lt;/strong&gt; &lt;a href="https://seismicpixels.com/ai-coding-hype-vs-reality/" rel="noopener noreferrer"&gt;AI-heavy codebases show 39% higher code churn&lt;/a&gt; — code reverted or heavily rewritten within two weeks. LLMs have no persistent memory of the constraints they set twenty prompts ago. They redefine &lt;code&gt;User&lt;/code&gt; in three files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hidden state.&lt;/strong&gt; The "backend" is a black-box datastore the tool provisions. You can't run migrations. You can't add a trigger. You can't set up &lt;a href="https://supabase.com/docs/guides/database/postgres/row-level-security" rel="noopener noreferrer"&gt;Supabase-style RLS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stack lock-in.&lt;/strong&gt; Most tools are aggressively opinionated. v0 outputs React + Tailwind. If you need Expo, native modules, or actual app store distribution, you're outside the happy path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No distribution story.&lt;/strong&gt; &lt;a href="https://docs.expo.dev/submit/introduction/" rel="noopener noreferrer"&gt;Docs for &lt;code&gt;eas submit&lt;/code&gt;&lt;/a&gt; exist because submission is a real, human-mediated process. No prompt generates it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The workflow that actually ships
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Start from a template that owns the boring stuff&lt;/span&gt;
git clone &amp;lt;template-repo&amp;gt; my-app
&lt;span class="nb"&gt;cd &lt;/span&gt;my-app
bun &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# 2. Point your agent at the repo&lt;/span&gt;
&lt;span class="c"&gt;# Claude Code, Codex, Cursor, whatever&lt;/span&gt;
&lt;span class="c"&gt;# Templates ship with .claude/skills/ so the agent knows the conventions&lt;/span&gt;

&lt;span class="c"&gt;# 3. Extend, don't invent&lt;/span&gt;
&lt;span class="c"&gt;# "Add a photo upload feature to the events screen"&lt;/span&gt;
&lt;span class="c"&gt;# "Wire the settings screen to the existing Stripe customer portal"&lt;/span&gt;

&lt;span class="c"&gt;# 4. Ship&lt;/span&gt;
eas build &lt;span class="nt"&gt;--platform&lt;/span&gt; all
eas submit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent is in this workflow. It just isn't inventing the architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  The split that works
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The template owns things like this:&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/supabase&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;requireAuth&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;uploadNote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;requireAuth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;notes&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// The agent adds features on top:&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;shareNoteAsPdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;noteId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;note&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getNote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;noteId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;renderPdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;note&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;uploadNote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;note&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The template's &lt;code&gt;supabase&lt;/code&gt;, &lt;code&gt;requireAuth&lt;/code&gt;, and &lt;code&gt;storage.from("notes")&lt;/code&gt; are load-bearing infrastructure. They already work. They already have RLS. The agent's job is the last function, not the first three.&lt;/p&gt;

&lt;h2&gt;
  
  
  When prompt-to-app is actually fine
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Marketing pages and landing sites (v0 is great for this)&lt;/li&gt;
&lt;li&gt;Prototypes for design review&lt;/li&gt;
&lt;li&gt;One-off internal tools without auth or payments&lt;/li&gt;
&lt;li&gt;Learning what components look like&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Prompt-to-app is a rendering technology. It's not a shipping technology. Use it for what it's good at.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: Are prompt-to-app tools useless?&lt;/strong&gt;&lt;br&gt;
No. Great for prototypes and marketing pages. Poor fit for shipping real mobile apps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Won't they get better?&lt;/strong&gt;&lt;br&gt;
Some of it, over years. Code signing and App Store review aren't things LLMs prompt their way through. Bet on agents inside real projects, not agents inventing them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: What stack should I pick?&lt;/strong&gt;&lt;br&gt;
Expo, TypeScript, Supabase, NativeWind, Stripe (+ Apple IAP for digital goods). Boring, well-documented, agent-friendly.&lt;/p&gt;




&lt;p&gt;I keep a set of Expo + Supabase templates wired for exactly this workflow at &lt;a href="https://www.applighter.com/apps?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=prompt-to-app-is-a-lie" rel="noopener noreferrer"&gt;Applighter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What's the wall that cost you the most time — auth, payments, or submission? Drop it in the comments.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>expo</category>
      <category>supabase</category>
      <category>ai</category>
    </item>
    <item>
      <title>AI Coding Agents Need a Foundation, Not a Canvas</title>
      <dc:creator>Russel Dsouza</dc:creator>
      <pubDate>Wed, 01 Jul 2026 13:10:48 +0000</pubDate>
      <link>https://dev.to/russel_dsouza_bd584a3cb2a/ai-coding-agents-need-a-foundation-not-a-canvas-2j1e</link>
      <guid>https://dev.to/russel_dsouza_bd584a3cb2a/ai-coding-agents-need-a-foundation-not-a-canvas-2j1e</guid>
      <description>&lt;ul&gt;
&lt;li&gt;Same prompt, two repos: &lt;strong&gt;blank Expo repo → 47min / $5.20 / un-mergeable&lt;/strong&gt;; wired foundation → &lt;strong&gt;11min / $0.85 / mergeable&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The model didn't get smarter. The repo got more legible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AGENTS.md helps ~4% (hand-written) or hurts 2–3% (LLM-generated), per a 138-repo / 5,694-PR study&lt;/strong&gt;. Both add &amp;gt;20% to token cost. It's not the fix.&lt;/li&gt;
&lt;li&gt;A real foundation ships &lt;strong&gt;one visible convention per cross-cutting concern&lt;/strong&gt;, typed boundaries, one way to do each thing, pre-installed skills, and a working end-to-end path.&lt;/li&gt;
&lt;li&gt;The evaluation heuristic: &lt;strong&gt;"Open the repo and ask Claude Code to add a screen. If the first thing the agent does is install three packages, the foundation is decorative."&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Cold open
&lt;/h2&gt;

&lt;p&gt;I had a working theory that the gap between coding agents that "feel useful" and coding agents that "feel like a coworker" was a model gap. Smarter base model, better tool use, longer context window. The usual story.&lt;/p&gt;

&lt;p&gt;Then I ran the same prompt against two different repos and the theory died on contact.&lt;/p&gt;

&lt;p&gt;The prompt was: &lt;em&gt;"Add a weekly summary screen that fetches the last seven days of data from Supabase and renders a bar chart."&lt;/em&gt; Mid-tier feature, nothing exotic. I ran it against a fresh &lt;code&gt;npx create-expo-app&lt;/code&gt; and against a fully wired React Native / Expo / Supabase foundation.&lt;/p&gt;

&lt;p&gt;Same model. Same prompt. Two completely different sessions.&lt;/p&gt;

&lt;p&gt;On the blank repo, the agent installed three chart libraries, picked one, then quietly imported a different one in the second file it wrote. It hardcoded the Supabase URL. It invented an &lt;code&gt;api/&lt;/code&gt; folder, then later invented a &lt;code&gt;services/&lt;/code&gt; folder, then never used either. After 47 minutes and roughly $5.20 in tokens, it produced 600 lines of code I would not merge into anything.&lt;/p&gt;

&lt;p&gt;On the foundation, the agent did something that, at first, felt anticlimactic. It opened one existing screen, read it, and then wrote one new screen that looked exactly like the existing one. Same chart primitive. Same data fetcher. Same file structure. 11 minutes. About 85 cents in tokens. Mergeable on the first read.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The model hadn't gotten smarter. The repo had gotten more legible.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The blank-canvas problem, named
&lt;/h2&gt;

&lt;p&gt;Every coding agent — Claude Code, Cursor, Codex, Windsurf, the lot — is doing roughly the same thing under the hood. It reads the repo, then writes code that "fits." When the repo has shape, "fits" means matching the shape. When the repo has no shape, "fits" means matching the median of the public internet.&lt;/p&gt;

&lt;p&gt;The public internet is a thousand React Native tutorials, each of which makes different micro-decisions. State library: Redux, Zustand, Jotai, Recoil, Context. Data fetching: TanStack Query, SWR, raw fetch, Supabase client wrappers. Navigation: Expo Router, React Navigation, stack vs. tabs vs. drawer. Theming: NativeWind, styled-components, StyleSheet, restyle. The "median" picks one of each, often differently per file.&lt;/p&gt;

&lt;p&gt;This is the blank-canvas problem. The agent does not fail because it is dumb. It fails because every choice is open and the choices don't compose.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AGENTS.md cul-de-sac
&lt;/h2&gt;

&lt;p&gt;The current industry answer is &lt;code&gt;AGENTS.md&lt;/code&gt; — a Markdown file at the root of the repo describing your conventions. I have written several. They help, in the way that a sticky note on the fridge helps. They do not solve the problem.&lt;/p&gt;

&lt;p&gt;The numbers are unkind. A study earlier this year analyzed &lt;code&gt;AGENTS.md&lt;/code&gt; impact across 138 repositories and 5,694 pull requests. The headline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LLM-generated &lt;code&gt;AGENTS.md&lt;/code&gt; files &lt;strong&gt;hurt&lt;/strong&gt; agent performance by 2–3%.&lt;/li&gt;
&lt;li&gt;Hand-written &lt;code&gt;AGENTS.md&lt;/code&gt; files improved performance by &lt;strong&gt;4%&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Both raised token costs by &lt;strong&gt;20%+&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;4% is not the breakthrough you were hoping for. It's noise.&lt;/p&gt;

&lt;p&gt;The intuition behind why is straightforward once you sit with it: prose about code is a weaker signal than code itself. An agent that reads three screens which all use the same data fetcher infers, very confidently, that the fourth screen should use the same fetcher. An agent that reads a sentence in &lt;code&gt;AGENTS.md&lt;/code&gt; saying "use the data fetcher in &lt;code&gt;lib/&lt;/code&gt;" sometimes does, sometimes doesn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The pattern is the prior. The &lt;code&gt;AGENTS.md&lt;/code&gt; is a cache.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What an actual agent-ready foundation contains
&lt;/h2&gt;

&lt;p&gt;The phrase "agent-ready" is doing a lot of work in boilerplate marketing. Here is what it should mean, concretely:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Visible conventions
&lt;/h3&gt;

&lt;p&gt;Every cross-cutting concern — auth, data fetching, navigation, theming, state, payments, push — appears in at least one fully wired screen. The agent has a working example to copy. Not three contradictory examples. Not one half-finished example. One canonical example.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Typed boundaries
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Supabase generated types — the agent cannot fake its way past these&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Database&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;public&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Tables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;daily_summaries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;Row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nl"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nl"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nl"&gt;total_calories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="c1"&gt;// ...&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="nl"&gt;Insert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="nl"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the agent calls a column that doesn't exist, &lt;code&gt;tsc&lt;/code&gt; fails. The foundation makes the type system enforce the prior.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. One way to do each thing
&lt;/h3&gt;

&lt;p&gt;If your foundation has both Redux and Zustand, the agent will use both. If it has only Zustand, the agent will use Zustand. Foundations make choices the agent doesn't have to.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Pre-installed skills and slash commands
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.claude/skills/
  add-screen.md           — knows where screens go, how they're wired
  wire-supabase-rpc.md    — knows how RPCs are exposed
  add-stripe-product.md   — knows the webhook layout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A skill is a scoped operation the agent can invoke directly. It is not prompt engineering. It is the foundation describing what it lets you do.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. A working end-to-end path
&lt;/h3&gt;

&lt;p&gt;Auth → database read → typed UI → push notification, all wired up once. The agent reads the whole chain in a single context window and now has a template for every future feature that touches the chain.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a real foundation looks like on disk
&lt;/h2&gt;

&lt;p&gt;Here's what a real React Native + Expo + Supabase foundation looks like when an agent opens it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;your-app/
├── app/                        # Expo Router screens
│   ├── (tabs)/
│   │   ├── index.tsx           # Home + today's entries
│   │   ├── add.tsx             # Camera + analysis flow
│   │   └── stats.tsx           # Daily summary chart
│   └── auth/
│       └── login.tsx           # OAuth + email + Apple
├── modules/
│   ├── db/
│   │   ├── supabaseClient.ts
│   │   └── supabaseServer.ts
│   ├── auth/
│   │   └── useAuth.ts
│   └── vision/
│       └── analyzeFood.ts      # Vision API integration
├── supabase/
│   └── migrations/             # Real, dated, ordered migrations
├── components/                 # NativeWind primitives
└── .claude/
    └── skills/                 # Scoped agent operations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the agent opens this and you ask for a weekly-summary feature, it reads &lt;code&gt;stats.tsx&lt;/code&gt;, reuses the chart primitive in &lt;code&gt;components/&lt;/code&gt;, calls the same &lt;code&gt;supabaseServer&lt;/code&gt; client, drops the screen in &lt;code&gt;(tabs)/&lt;/code&gt;, and follows the same migration pattern if it needs a new column.&lt;/p&gt;

&lt;p&gt;The agent never asks "what state library should I use?" because the repo answered the question.&lt;/p&gt;

&lt;h2&gt;
  
  
  The economics, in detail
&lt;/h2&gt;

&lt;p&gt;I tracked twelve sessions across three teammates — six from blank repos, six from foundations — using the same model and same prompt categories. Averages:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Blank repo&lt;/th&gt;
&lt;th&gt;Foundation&lt;/th&gt;
&lt;th&gt;Delta&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tokens per shipped feature&lt;/td&gt;
&lt;td&gt;$4.30&lt;/td&gt;
&lt;td&gt;$0.95&lt;/td&gt;
&lt;td&gt;4.5× cheaper&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wall time per feature&lt;/td&gt;
&lt;td&gt;42 min&lt;/td&gt;
&lt;td&gt;9 min&lt;/td&gt;
&lt;td&gt;4.7× faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Diffs requiring full rewrite&lt;/td&gt;
&lt;td&gt;4 of 6&lt;/td&gt;
&lt;td&gt;0 of 6&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hallucinated imports&lt;/td&gt;
&lt;td&gt;11 total&lt;/td&gt;
&lt;td&gt;1 total&lt;/td&gt;
&lt;td&gt;11× fewer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is not a controlled study. It is, however, consistent with what teammates and customers report. The gap is real and it is wide.&lt;/p&gt;

&lt;p&gt;If you ship one app a year, the gap doesn't matter much — you'll spend a month either way. If you ship three or four, the gap is the difference between "we can" and "we can't."&lt;/p&gt;

&lt;h2&gt;
  
  
  What this does not mean
&lt;/h2&gt;

&lt;p&gt;Two clarifications, because the foundation argument gets oversold.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A foundation does not replace judgment.&lt;/strong&gt; The agent still ships things you have to review. The foundation just narrows what the agent can ship.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A foundation is not forever.&lt;/strong&gt; Two years from now the model will be stronger, the framework will have shifted, and today's foundation will feel constraining. Throw it away when it does. Until then, it earns its keep every session.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to evaluate any foundation you're considering
&lt;/h2&gt;

&lt;p&gt;Whether you're considering a paid template, a free GitHub template, or something a friend shipped — the test is the same:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Open the repo and ask Claude Code to add a screen. If the first thing the agent does is install three packages, the foundation is decorative.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Other tests, in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Count real screens.&lt;/strong&gt; Five or more, all using the same conventions, is the bar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grep for &lt;code&gt;any&lt;/code&gt;.&lt;/strong&gt; If the data layer leaks &lt;code&gt;any&lt;/code&gt;, the agent has no priors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Find the skills directory.&lt;/strong&gt; No skills, no scoped operations, the foundation is half-built.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the migrations folder.&lt;/strong&gt; Real, dated migrations are a foundation signal. A single &lt;code&gt;init.sql&lt;/code&gt; is a boilerplate signal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run the first session.&lt;/strong&gt; Token cost and wall-clock time will tell you everything within 15 minutes.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is a foundation just a fancy boilerplate?&lt;/strong&gt;&lt;br&gt;
No. A boilerplate is the packaging. A foundation is the wiring. Most boilerplates are not foundations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why doesn't a great AGENTS.md fix this?&lt;/strong&gt;&lt;br&gt;
It helps about 4%, per the 138-repo study. The pattern is a much stronger prior than prose about the pattern.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this only apply to React Native?&lt;/strong&gt;&lt;br&gt;
No. It applies to every stack. React Native is the worst case because its ecosystem is the most fragmented, so the gap is largest there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should I buy or build the foundation?&lt;/strong&gt;&lt;br&gt;
Build if you'll ship three or more apps on the same stack. Buy if this is your first or second.&lt;/p&gt;




&lt;p&gt;If you take one thing away: do not hand an AI coding agent an empty room and ask it to build a house. Give it a foundation. Then ask it to add the rooms.&lt;/p&gt;

&lt;p&gt;For the longer breakdown — including the specific "one way to do each thing" heuristics I use to audit a foundation — see &lt;a href="https://www.applighter.com/blog?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=ai-coding-agent-needs-foundation-2026" rel="noopener noreferrer"&gt;the Applighter blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What's the weirdest thing an AI coding agent has done in your blank repo? Drop it in the comments — I'm collecting the failure modes for a follow-up. (Bonus points for a token-cost screenshot.)&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>expo</category>
      <category>supabase</category>
      <category>ai</category>
    </item>
    <item>
      <title>Buy a React Native Template or Build From Scratch in 2026?</title>
      <dc:creator>Russel Dsouza</dc:creator>
      <pubDate>Tue, 30 Jun 2026 09:59:09 +0000</pubDate>
      <link>https://dev.to/russel_dsouza_bd584a3cb2a/buy-a-react-native-template-or-build-from-scratch-in-2026-j93</link>
      <guid>https://dev.to/russel_dsouza_bd584a3cb2a/buy-a-react-native-template-or-build-from-scratch-in-2026-j93</guid>
      <description>&lt;p&gt;You've run &lt;code&gt;npx create-expo-app&lt;/code&gt;, and three days later you're configuring NativeWind v4 PostCSS, debugging Supabase deep-link callbacks, and writing your fifth token-refresh &lt;code&gt;useEffect&lt;/code&gt;. The decision hits: keep grinding, or buy a $79 React Native template and skip to the actual product?&lt;/p&gt;

&lt;p&gt;This post is a decision framework with the actual numbers. No marketing — the math leans toward buying, and the rest of this post explains exactly when it doesn't.&lt;/p&gt;

&lt;p&gt;Buy when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The app is a recognizable pattern (auth + DB + payments + a vertical UI)&lt;/li&gt;
&lt;li&gt;You're solo or a two-person team&lt;/li&gt;
&lt;li&gt;Your time is worth more than $50/hour&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Build from scratch when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The UX is genuinely novel&lt;/li&gt;
&lt;li&gt;You have hard compliance (HIPAA, PCI L1, SOC 2)&lt;/li&gt;
&lt;li&gt;You're learning React Native and the setup is the point&lt;/li&gt;
&lt;li&gt;You're at 1M+ user scale on day one&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The setup tax
&lt;/h2&gt;

&lt;p&gt;Foundation work, before any product code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Expo + TS + ESLint + Prettier ......... 4h
Expo Router v4 (typed routes) ......... 6h
Supabase auth (email/Apple/Google) .... 14h
NativeWind v4 + dark mode ............. 8h
TanStack Query + Zustand .............. 6h
Stripe / RevenueCat ................... 16h
Push (APNs + FCM via Expo) ............ 8h
Supabase Storage + RLS ................ 6h
Sentry + PostHog ...................... 4h
EAS Build + iOS/Android signing ....... 8h
─────────────────────────────────────────
Total .................................. ~80h
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At $75/hr indie rate that's &lt;strong&gt;$6,000 of your time&lt;/strong&gt;. At $150/hr US contract, $12,000. Most of those hours are commodity work — you are not building a moat, you are configuring &lt;code&gt;app.json&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Template price tiers in 2026
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$0–29     screen kits, free boilerplates (no backend, stale fast)
$49–99    full-stack with real backend (Supabase/Firebase/Node)
$199–499  vertical clones (food delivery, marketplace, dating)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a $79 template saves 60 of those 80 hours, the effective hourly rate for from-scratch to win is under $1.32/hour. Don't be that cheap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Buy vs. build at a glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Factor&lt;/th&gt;
&lt;th&gt;Build&lt;/th&gt;
&lt;th&gt;Buy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Time to deployable build&lt;/td&gt;
&lt;td&gt;3–6 weeks&lt;/td&gt;
&lt;td&gt;1–2 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cash cost&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$49–499&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time cost&lt;/td&gt;
&lt;td&gt;80–200h&lt;/td&gt;
&lt;td&gt;2–8h customization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code understood day 1&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;60–80% (100% after a week)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Architecture quality&lt;/td&gt;
&lt;td&gt;Your choices&lt;/td&gt;
&lt;td&gt;Template's choices&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security posture&lt;/td&gt;
&lt;td&gt;What you remember&lt;/td&gt;
&lt;td&gt;What was shipped&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for&lt;/td&gt;
&lt;td&gt;Novel UX, learning, compliance&lt;/td&gt;
&lt;td&gt;MVPs, vertical clones, agency work&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The buyer's checklist
&lt;/h2&gt;

&lt;p&gt;Before you buy, verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Is the backend real or mocked?&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"mockApi&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;fixtures"&lt;/span&gt; src/

&lt;span class="c"&gt;# 2. React Native + Expo version&lt;/span&gt;
&lt;span class="nb"&gt;cat &lt;/span&gt;package.json | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"react-native|expo"&lt;/span&gt;
&lt;span class="c"&gt;# Floor in 2026: RN 0.81, Expo SDK 54&lt;/span&gt;

&lt;span class="c"&gt;# 3. Leaked keys&lt;/span&gt;
git log &lt;span class="nt"&gt;-p&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s2"&gt;"sk_live|service_role|AKIA"&lt;/span&gt;

&lt;span class="c"&gt;# 4. TypeScript strict mode&lt;/span&gt;
&lt;span class="nb"&gt;cat &lt;/span&gt;tsconfig.json | &lt;span class="nb"&gt;grep &lt;/span&gt;strict

&lt;span class="c"&gt;# 5. Last commit recency&lt;/span&gt;
git log &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;%cd

&lt;span class="c"&gt;# 6. License terms&lt;/span&gt;
&lt;span class="nb"&gt;cat &lt;/span&gt;LICENSE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a template fails on (1) — backend is mocked — you don't have a template, you have a screen kit. You will rebuild the entire backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "production-ready" should actually mean
&lt;/h2&gt;

&lt;p&gt;Six bars a template should clear before it can use the phrase:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A Postgres schema with RLS you can &lt;code&gt;supabase db push&lt;/code&gt; — see &lt;a href="https://supabase.com/docs/guides/auth/row-level-security" rel="noopener noreferrer"&gt;Supabase RLS docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Auth flows that pass App Store review (Apple Sign-In mandatory if any other social)&lt;/li&gt;
&lt;li&gt;Server routes for anything sensitive (no API keys on device)&lt;/li&gt;
&lt;li&gt;Error / loading / empty states wired everywhere&lt;/li&gt;
&lt;li&gt;EAS Build profiles producing signed iOS + Android binaries — see &lt;a href="https://docs.expo.dev/build/introduction/" rel="noopener noreferrer"&gt;EAS Build docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AI agents (Claude Code, Cursor) can extend it without breaking the architecture&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The sixth is the 2026 bar that didn't exist in 2023. AI agents do their best work on known-good repos. Templates that confuse agents lose to ones that don't.&lt;/p&gt;

&lt;h2&gt;
  
  
  When buying wins
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Your app is "X for Y" where both X and Y already exist&lt;/li&gt;
&lt;li&gt;Solo / two-person team&lt;/li&gt;
&lt;li&gt;Agency / contractor billing a client (license permitting)&lt;/li&gt;
&lt;li&gt;You want a security baseline you didn't write at 1 AM&lt;/li&gt;
&lt;li&gt;You're driving development with AI agents&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When building wins
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Novel UX with no obvious analog&lt;/li&gt;
&lt;li&gt;Hard compliance constraints&lt;/li&gt;
&lt;li&gt;Learning React Native — the setup &lt;em&gt;is&lt;/em&gt; the point&lt;/li&gt;
&lt;li&gt;Extreme scale on day one&lt;/li&gt;
&lt;li&gt;Template stack is wrong for you (e.g., you need Firebase, template is Supabase)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A two-minute decision framework
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Screen architecture obvious in 5 min of whiteboard? → buy&lt;/li&gt;
&lt;li&gt;App describable as "X for Y"? → buy&lt;/li&gt;
&lt;li&gt;Timeline &amp;lt; 60 days? → buy&lt;/li&gt;
&lt;li&gt;Billing a client &amp;gt; $5,000? → buy&lt;/li&gt;
&lt;li&gt;Learning React Native? → build the first, buy the second&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Two or more "buy" answers and you don't need a calculator.&lt;/p&gt;

&lt;h2&gt;
  
  
  Disclosure
&lt;/h2&gt;

&lt;p&gt;I work on production-grade React Native templates ($49–99 on RN 0.81 / Expo SDK 54 / Supabase). Bias acknowledged. The math still leans toward buying for most indie apps — and when it doesn't, the post above tells you exactly why.&lt;/p&gt;

&lt;p&gt;Whatever template you evaluate, run the buyer's checklist above. A real template ships a real backend (schema, RLS, server routes), passing App Store review, with EAS Build profiles ready to go. If it doesn't ship those, the price you spent on it bought you nothing.&lt;/p&gt;

&lt;p&gt;If none of the templates on the market match your app shape, build from scratch. That's the only honest answer to the buy-vs-build question.&lt;/p&gt;




&lt;p&gt;For the longer breakdown — the FAQ and the line-by-line "what a real template ships" inventory — see &lt;a href="https://www.applighter.com/blog?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=buy-vs-build-react-native-2026" rel="noopener noreferrer"&gt;the Applighter blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What's the foundation work that ate the most hours on your last React Native project? Drop it in the comments — I'm collecting the unglamorous setup tasks indie devs underestimate for a follow-up.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>expo</category>
      <category>supabase</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Mobile App Security Best Practices in 2026</title>
      <dc:creator>Russel Dsouza</dc:creator>
      <pubDate>Mon, 29 Jun 2026 10:04:58 +0000</pubDate>
      <link>https://dev.to/russel_dsouza_bd584a3cb2a/mobile-app-security-best-practices-in-2026-d0e</link>
      <guid>https://dev.to/russel_dsouza_bd584a3cb2a/mobile-app-security-best-practices-in-2026-d0e</guid>
      <description>&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Most mobile breaches aren't sophisticated.&lt;/strong&gt; They're a hardcoded API key, a forgotten debug flag, or a token in plaintext &lt;code&gt;AsyncStorage&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The OWASP Mobile Top 10 (2024) is your checklist — work it every release.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tokens live in the OS keychain&lt;/strong&gt; via &lt;code&gt;expo-secure-store&lt;/code&gt;, never &lt;code&gt;AsyncStorage&lt;/code&gt;. Period.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Certificate pinning&lt;/strong&gt; for sensitive endpoints, pinned to the &lt;strong&gt;SPKI hash&lt;/strong&gt;, with a backup pin and a rotation plan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI-generated code is untrusted input&lt;/strong&gt; — review auth, storage, and network code with the same rigor as a new contributor's PR.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI is where security lives:&lt;/strong&gt; &lt;code&gt;semgrep&lt;/code&gt; + &lt;code&gt;eslint-plugin-security&lt;/code&gt; + &lt;code&gt;npm audit&lt;/code&gt; + MobSF on every release artifact.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Mobile attacks are up. Regulators are watching. AI is writing more of your code than ever — and the patterns it reproduces aren't always the secure ones. Here's the practical checklist I run through for every React Native / Expo app, organized around the OWASP Mobile Top 10 (2024).&lt;/p&gt;

&lt;p&gt;This is the working version of a longer guide — focused on what to actually change in your codebase this week.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Credentials: nothing sensitive in the bundle, nothing sensitive in AsyncStorage
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Don't&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;AsyncStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;access_token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Do&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;SecureStore&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;expo-secure-store&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SecureStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItemAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;access_token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;keychainAccessible&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SecureStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WHEN_UNLOCKED_THIS_DEVICE_ONLY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anything in the bundle can be extracted with &lt;code&gt;apktool&lt;/code&gt; in minutes. Anything in &lt;code&gt;AsyncStorage&lt;/code&gt; is plaintext on disk. Tokens go in the OS keychain via &lt;code&gt;expo-secure-store&lt;/code&gt; or &lt;code&gt;react-native-keychain&lt;/code&gt;. Period.&lt;/p&gt;

&lt;p&gt;Refresh tokens rotate on every use. Access tokens live 15 minutes. The backend is the trust boundary, not the client.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Supply chain: assume your dependencies are hostile
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# In CI, on every PR&lt;/span&gt;
npm ci
npm audit &lt;span class="nt"&gt;--audit-level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;high
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A clean &lt;code&gt;package.json&lt;/code&gt; doesn't mean a clean app. Post-install scripts run with your dev-machine privileges. Native modules run with full app privileges.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lockfiles in source control. &lt;code&gt;npm ci&lt;/code&gt; in CI, never &lt;code&gt;npm install&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add Socket or Snyk for behavioral analysis (what npm audit misses).&lt;/li&gt;
&lt;li&gt;Audit native modules personally if they touch storage, networking, or credentials.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Auth: PKCE, short JWTs, server-side authorization
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;AuthSession&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;expo-auth-session&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// PKCE is the default in expo-auth-session — don't disable it.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;AuthSession&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AuthRequest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;openid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;profile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;usePKCE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;redirectUri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OAuth 2.0 with PKCE for third-party identity. JWTs with 15-minute access tokens and rotated refresh tokens. &lt;strong&gt;Every endpoint validates the caller server-side.&lt;/strong&gt; Hiding UI is not authorization.&lt;/p&gt;

&lt;p&gt;Add MFA via &lt;code&gt;expo-local-authentication&lt;/code&gt; for anything touching payments, identity, or health data.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Input validation: deeplinks are user input
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Treat the URL params as hostile&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleDeepLink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;action&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;a-z_&lt;/span&gt;&lt;span class="se"&gt;]{1,32}&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;KNOWN_ACTIONS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;routeTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deeplinks, push payloads, clipboard, QR codes, WebView messages — all untrusted. Validate type, length, format. Parameterized queries for local SQLite. &lt;code&gt;originWhitelist&lt;/code&gt; on every &lt;code&gt;WebView&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. TLS 1.3 + certificate pinning
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- android/app/src/main/res/xml/network_security_config.xml --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;network-security-config&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;domain-config&lt;/span&gt; &lt;span class="na"&gt;cleartextTrafficPermitted=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;domain&lt;/span&gt; &lt;span class="na"&gt;includeSubdomains=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;api.yourapp.com&lt;span class="nt"&gt;&amp;lt;/domain&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;pin-set&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;pin&lt;/span&gt; &lt;span class="na"&gt;digest=&lt;/span&gt;&lt;span class="s"&gt;"SHA-256"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{base64-spki-hash}&lt;span class="nt"&gt;&amp;lt;/pin&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;pin&lt;/span&gt; &lt;span class="na"&gt;digest=&lt;/span&gt;&lt;span class="s"&gt;"SHA-256"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{backup-spki-hash}&lt;span class="nt"&gt;&amp;lt;/pin&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/pin-set&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/domain-config&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/network-security-config&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pin to the SPKI hash, not the leaf cert. Ship a backup pin. Have a rotation plan. Reject TLS 1.0/1.1 server-side.&lt;/p&gt;

&lt;p&gt;In Expo, &lt;code&gt;usesCleartextTraffic: false&lt;/code&gt;. Verify no &lt;code&gt;allowsArbitraryLoads&lt;/code&gt; snuck into production.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Privacy controls aren't optional
&lt;/h2&gt;

&lt;p&gt;Maintain a data inventory. Apply data minimization. Request permissions just-in-time with context. Build account-delete-and-export flows that actually delete and export. Audit analytics/ad SDKs quarterly — they change practices on their schedule.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Binary hardening: Hermes, R8/ProGuard, App Attest
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Ship Hermes bytecode — much harder to reverse than plain JS.&lt;/li&gt;
&lt;li&gt;R8 with shrinking and obfuscation on Android.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;babel-plugin-transform-remove-console&lt;/code&gt; in release builds.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;jail-monkey&lt;/code&gt; for rooted/jailbroken detection (signal, not block).&lt;/li&gt;
&lt;li&gt;Apple's App Attest + Google's Play Integrity API to verify the app calling your backend is the one you shipped.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  8. Configuration hygiene
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;__DEV__&lt;/code&gt; guards on every debug code path.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;android:exported="true"&lt;/code&gt; only when truly needed.&lt;/li&gt;
&lt;li&gt;URL schemes and Universal Links audited as entry points.&lt;/li&gt;
&lt;li&gt;CI check that fails the build if known test-account strings hit the binary.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  9. Encrypted storage, intentional sensitivity tiers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Sensitivity&lt;/th&gt;
&lt;th&gt;Storage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Credentials, keys&lt;/td&gt;
&lt;td&gt;iOS Keychain / Android Keystore via &lt;code&gt;expo-secure-store&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Structured PII&lt;/td&gt;
&lt;td&gt;SQLCipher or encrypted Realm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Non-sensitive&lt;/td&gt;
&lt;td&gt;Regular filesystem or &lt;code&gt;AsyncStorage&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Disable backup for sensitive paths. Mask app-switcher screenshots on sensitive screens via &lt;code&gt;expo-screen-capture&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Modern crypto, vetted libraries
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;randomBytes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-native-quick-crypto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// AES-256-GCM. Never CBC without auth. Never ECB. Ever.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Argon2id for password hashing. HKDF for key derivation. SHA-1 and MD5 are dead. Start tracking your post-quantum migration — NIST's ML-KEM and ML-DSA are finalized.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Treat AI-generated code as untrusted input
&lt;/h2&gt;

&lt;p&gt;This is the one most security frameworks haven't caught up to. LLMs reproduce the most common pattern in their training data — often the most common &lt;em&gt;flawed&lt;/em&gt; pattern.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maintain a &lt;code&gt;.cursorrules&lt;/code&gt; or &lt;code&gt;.github/copilot-instructions.md&lt;/code&gt; with your secure defaults.&lt;/li&gt;
&lt;li&gt;Review AI-generated auth, storage, and network code with the same rigor as a new contributor's PR.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;semgrep&lt;/code&gt; and &lt;code&gt;eslint-plugin-security&lt;/code&gt; on AI output before merge.&lt;/li&gt;
&lt;li&gt;Pen-test AI-generated payment and auth flows specifically.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  12. CI is where security lives
&lt;/h2&gt;

&lt;p&gt;Every PR:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;semgrep&lt;/code&gt;, &lt;code&gt;eslint-plugin-security&lt;/code&gt;, Android Lint&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm audit&lt;/code&gt; + Socket/Snyk&lt;/li&gt;
&lt;li&gt;MobSF on release-build artifacts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Annually: pen test. Quarterly: SDK audit. Always: an incident response plan that includes key rotation, token revocation, and an OTA push.&lt;/p&gt;




&lt;p&gt;Most mobile breaches aren't sophisticated. They're a hardcoded key, a forgotten debug flag, a plaintext token. The OWASP Top 10 is your checklist — work it every release.&lt;/p&gt;

&lt;p&gt;For the longer breakdown — including the AI-safe-code generation patterns and the full OWASP-mapped CI workflow — see &lt;a href="https://www.rapidnative.com/blogs?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=mobile-app-security-2026" rel="noopener noreferrer"&gt;the RapidNative blog&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;What's the security gotcha you've shipped to production and then quietly patched? Drop it in the comments — I'm collecting the failure modes that don't make it into the OWASP examples for a follow-up.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>security</category>
      <category>mobile</category>
      <category>expo</category>
    </item>
    <item>
      <title>Stop Reinventing React Native Auth — Buy a Template</title>
      <dc:creator>Russel Dsouza</dc:creator>
      <pubDate>Mon, 29 Jun 2026 09:43:48 +0000</pubDate>
      <link>https://dev.to/russel_dsouza_bd584a3cb2a/stop-reinventing-react-native-auth-buy-a-template-4h9n</link>
      <guid>https://dev.to/russel_dsouza_bd584a3cb2a/stop-reinventing-react-native-auth-buy-a-template-4h9n</guid>
      <description>&lt;ul&gt;
&lt;li&gt;Building React Native auth from scratch routinely takes &lt;strong&gt;60–120 hours&lt;/strong&gt; and ships subtle bugs (the Apple Sign In &lt;code&gt;fullName&lt;/code&gt; trap is the classic).&lt;/li&gt;
&lt;li&gt;The real surface area is seven traps: AsyncStorage encryption, refresh-token mutex, three OAuth deep-link code paths, &lt;code&gt;fullName&lt;/code&gt; persistence, mandatory account deletion, magic-link rate limiting, token hashing at rest.&lt;/li&gt;
&lt;li&gt;A vetted template covers all seven for &lt;strong&gt;$99–$499&lt;/strong&gt;. Break-even is ~3 hours of saved work.&lt;/li&gt;
&lt;li&gt;Build it yourself only if you have enterprise IdP (SAML/Okta), HIPAA/FIDO2 constraints, or your company &lt;em&gt;is&lt;/em&gt; the auth product.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Every React Native auth implementation starts with "it's just &lt;code&gt;signInWithPassword&lt;/code&gt;" and ends 75 hours later debugging refresh-token rotation. This post is the field guide I wish I had before I built mobile auth from scratch three times.&lt;/p&gt;

&lt;h2&gt;
  
  
  The seven traps
&lt;/h2&gt;

&lt;p&gt;If you build auth from scratch in React Native, here's what bites you in production. None of these are in the happy-path tutorial.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. AsyncStorage is not encrypted
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 🚫 wrong&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;AsyncStorage&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@react-native-async-storage/async-storage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;AsyncStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;access_token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ right&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;SecureStore&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;expo-secure-store&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SecureStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItemAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;access_token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On iOS, SecureStore uses Keychain. On Android, EncryptedSharedPreferences. AsyncStorage is a plaintext SQLite blob.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Refresh-token rotation needs a mutex
&lt;/h3&gt;

&lt;p&gt;Supabase rotates refresh tokens on every use. Two concurrent API calls at the moment of expiry will both try to refresh, and one of them invalidates the other's freshly-minted token. You need a single-flight mutex around refresh:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;refreshPromise&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Session&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getValidSession&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;refreshPromise&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;refreshPromise&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;refreshPromise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refreshSession&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;refreshPromise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;refreshPromise&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. OAuth deep links are three code paths
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;iOS native build&lt;/strong&gt;: &lt;code&gt;ASWebAuthenticationSession&lt;/code&gt; via &lt;code&gt;expo-auth-session&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Android native build&lt;/strong&gt;: Chrome Custom Tabs via &lt;code&gt;expo-auth-session&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expo Go&lt;/strong&gt;: a polyfill that opens a system browser&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each one returns control through a different URL scheme. Universal Links on iOS, intent filters on Android, and an &lt;code&gt;exp://&lt;/code&gt; URL in Expo Go. Get any wrong and the user lands on a white screen.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Apple Sign In &lt;code&gt;fullName&lt;/code&gt; only returns on first sign-in
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;credential&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;AppleAuthentication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signInAsync&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;requestedScopes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;AppleAuthentication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AppleAuthenticationScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FULL_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;AppleAuthentication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AppleAuthenticationScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EMAIL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// credential.fullName is null on EVERY subsequent sign-in.&lt;/span&gt;
&lt;span class="c1"&gt;// You MUST persist it the first time, server-side.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the bug I've shipped on three separate projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Account deletion is mandatory
&lt;/h3&gt;

&lt;p&gt;App Store guideline 5.1.1(v): you must offer in-app account deletion. That means a confirmation screen, a server endpoint that revokes all sessions, and a way to handle a GDPR data-export request. Skip it, fail review.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Rate limit your magic-link endpoint
&lt;/h3&gt;

&lt;p&gt;Without per-email and per-IP throttling, anyone can blow up your transactional email bill. The minimum pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RATE_LIMIT_WINDOW_MS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RATE_LIMIT_MAX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;recent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabaseServer&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;magic_link_tokens&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;created_at&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;exact&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;created_at&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;RATE_LIMIT_WINDOW_MS&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;recent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;RATE_LIMIT_MAX&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rate_limited&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7. Hash your tokens at rest
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tokenHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// store tokenHash, email the user `token`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your DB ever leaks, the rows are worthless.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cost, with numbers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workstream&lt;/th&gt;
&lt;th&gt;Hours from scratch&lt;/th&gt;
&lt;th&gt;Hours with a template&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Email + password screens&lt;/td&gt;
&lt;td&gt;6h&lt;/td&gt;
&lt;td&gt;0h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google OAuth (iOS + Android + Expo Go)&lt;/td&gt;
&lt;td&gt;8h&lt;/td&gt;
&lt;td&gt;1h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apple Sign In + name persistence&lt;/td&gt;
&lt;td&gt;6h&lt;/td&gt;
&lt;td&gt;1h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Magic link + email delivery&lt;/td&gt;
&lt;td&gt;10h&lt;/td&gt;
&lt;td&gt;1h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secure token storage + refresh mutex&lt;/td&gt;
&lt;td&gt;8h&lt;/td&gt;
&lt;td&gt;0h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Biometric unlock&lt;/td&gt;
&lt;td&gt;5h&lt;/td&gt;
&lt;td&gt;1h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Password reset + account deletion&lt;/td&gt;
&lt;td&gt;6h&lt;/td&gt;
&lt;td&gt;0h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schema + RLS policies&lt;/td&gt;
&lt;td&gt;8h&lt;/td&gt;
&lt;td&gt;0h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rate limiting + email DKIM&lt;/td&gt;
&lt;td&gt;6h&lt;/td&gt;
&lt;td&gt;1h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Testing&lt;/td&gt;
&lt;td&gt;12h&lt;/td&gt;
&lt;td&gt;4h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;75h&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;9h&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At $75/hour, that's $5,625 vs $675. Templates range from $99 to $499. The math isn't subtle.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a real template ships
&lt;/h2&gt;

&lt;p&gt;Production-grade React Native templates wire all seven traps above and ship them as default behavior. The baseline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;expo-secure-store&lt;/code&gt; token storage&lt;/li&gt;
&lt;li&gt;Refresh-token mutex&lt;/li&gt;
&lt;li&gt;Google + Apple + magic link + email/password&lt;/li&gt;
&lt;li&gt;Apple Sign In &lt;code&gt;fullName&lt;/code&gt; persistence&lt;/li&gt;
&lt;li&gt;Account deletion endpoint&lt;/li&gt;
&lt;li&gt;Rate-limited magic-link API with SHA-256 token hashing&lt;/li&gt;
&lt;li&gt;Supabase migrations with RLS policies on every user-scoped table&lt;/li&gt;
&lt;li&gt;Biometric unlock via &lt;code&gt;expo-local-authentication&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a template you're evaluating doesn't ship all of these, you're getting a UI kit with a database attached — not auth. Ask to see the migrations and the magic-link route before you spend any money.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to build it yourself
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Enterprise IdP (SAML / Okta) integration&lt;/li&gt;
&lt;li&gt;Compliance constraints (HIPAA-eligible IdPs, FIDO2 step-up)&lt;/li&gt;
&lt;li&gt;You're literally building an auth product&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Otherwise, you're paying a tax with zero offsetting return. The hours you spend rebuilding OAuth callbacks are hours your competitors are spending on features users actually see.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.expo.dev/guides/authentication/" rel="noopener noreferrer"&gt;Expo Authentication guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://supabase.com/docs/guides/auth" rel="noopener noreferrer"&gt;Supabase Auth docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://reactnative.dev/docs/security" rel="noopener noreferrer"&gt;React Native Security&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;For the longer breakdown — the actual template I use, the migrations, and the full magic-link route — see &lt;a href="https://www.applighter.com/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=stop-reinventing-react-native-auth-2026" rel="noopener noreferrer"&gt;Applighter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What's the auth gotcha that ate the most hours on your last React Native project? Drop it in the comments — I'm collecting the failure modes that don't make it into the docs for a follow-up.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>expo</category>
      <category>supabase</category>
      <category>authentication</category>
    </item>
    <item>
      <title>React Native AI app cost in 2026</title>
      <dc:creator>Russel Dsouza</dc:creator>
      <pubDate>Fri, 26 Jun 2026 09:13:20 +0000</pubDate>
      <link>https://dev.to/russel_dsouza_bd584a3cb2a/react-native-ai-app-cost-in-2026-acn</link>
      <guid>https://dev.to/russel_dsouza_bd584a3cb2a/react-native-ai-app-cost-in-2026-acn</guid>
      <description>&lt;p&gt;Every agency quote for a React Native AI app collapses into one fuzzy bracket: &lt;code&gt;$60K–$150K&lt;/code&gt;. That bracket is useless. Here's the actual line-item breakdown — hours, dollars, API bills — for what a &lt;strong&gt;React Native AI app cost&lt;/strong&gt; looks like in 2026.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Solo indie, from scratch:   436–832 hours = $32K–$62K
Agency, from scratch:                       $80K–$180K
Template starting point:    $79 + 20–60 hrs customization
Year-one ongoing (API + infra):             $3.6K–$30K
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The line items
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Discovery + IA + design — 60-100 hrs
&lt;/h3&gt;

&lt;p&gt;The part juniors think doesn't count. Data model. Routing graph. What an "AI session" means in your domain. Empty states. Permission denials. Offline behavior. &lt;strong&gt;Under-estimated by 4x in every indie project I've seen.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Auth + Supabase + RLS — 40-80 hrs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- The 30-line policy file that takes a full week to get right&lt;/span&gt;
&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="nv"&gt;"users can only see their own transcripts"&lt;/span&gt;
  &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;transcripts&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;select&lt;/span&gt;
  &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="nv"&gt;"users can insert their own transcripts"&lt;/span&gt;
  &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;transcripts&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;insert&lt;/span&gt;
  &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="k"&gt;check&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;-- ...repeat for every table, every action&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plus schema migrations, OAuth wiring, edge functions for secrets. The &lt;a href="https://supabase.com/docs/guides/auth/row-level-security" rel="noopener noreferrer"&gt;Supabase RLS guide&lt;/a&gt; is excellent but policy &lt;em&gt;design&lt;/em&gt; is on you.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. AI integration — 80-160 hrs
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;AI shape&lt;/th&gt;
&lt;th&gt;Hours&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Image → JSON (vision)&lt;/td&gt;
&lt;td&gt;60–100&lt;/td&gt;
&lt;td&gt;Camera, compression, retry, structured parsing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audio → transcript&lt;/td&gt;
&lt;td&gt;100–160&lt;/td&gt;
&lt;td&gt;Streaming chunks, partial results, background mode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Document → chat (RAG)&lt;/td&gt;
&lt;td&gt;120–200&lt;/td&gt;
&lt;td&gt;Chunking, embeddings, vector store, citations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  4. UI screens — 120-240 hrs
&lt;/h3&gt;

&lt;p&gt;A real AI app is not one chat screen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;onboarding (4–6 screens)&lt;/li&gt;
&lt;li&gt;history list + detail&lt;/li&gt;
&lt;li&gt;settings&lt;/li&gt;
&lt;li&gt;paywall + manage-subscription&lt;/li&gt;
&lt;li&gt;empty / error / offline states&lt;/li&gt;
&lt;li&gt;accessibility labels on everything&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Twenty-plus production screens, each needing a dark mode, a tablet layout, and a VoiceOver label. This is the block that swallows half the budget on every project I've seen.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Stripe + license grants — 40-60 hrs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The 200-line webhook handler nobody writes a tutorial for&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stripe-signature&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webhooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;constructEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checkout.session.completed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;grantLicense&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;customer.subscription.deleted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;revokeLicense&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// ...11 more cases for restore-purchases, refunds, disputes&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;iOS IAP restore flow alone is two days.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Push notifications — 16-32 hrs
&lt;/h3&gt;

&lt;p&gt;Topic logic, do-not-disturb, deep links, permission UX, silent-push retries. Expo's push service is great but the &lt;em&gt;policy&lt;/em&gt; of when to send is yours.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. EAS Build + store submission — 20-40 hrs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eas build &lt;span class="nt"&gt;--platform&lt;/span&gt; all &lt;span class="nt"&gt;--profile&lt;/span&gt; production
eas submit &lt;span class="nt"&gt;-p&lt;/span&gt; ios &lt;span class="nt"&gt;--latest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two commands. Forty hours of metadata, screenshots at every device size, privacy nutrition labels, ATT prompts, the "what does your AI actually do" review questions, one rejection.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.expo.dev/build/introduction/" rel="noopener noreferrer"&gt;EAS docs&lt;/a&gt; cover the build; nobody covers the rejection.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. QA + edge cases + accessibility — 60-120 hrs
&lt;/h3&gt;

&lt;p&gt;VoiceOver labels. RTL. Dark mode. Tablet. Tiny phones. 4,000-item scroll test. Apple genuinely checks accessibility now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recurring costs
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GPT-4o Vision:        ~$0.01 / image
OpenAI Whisper:       ~$0.006 / minute audio
Claude/GPT-4 chat:    $3–$15 / 1M input tokens
Supabase Pro:         $25 / month
EAS team plan:        $99 / month
pgvector (in Supabase):     $0
Pinecone (if you outgrow):  $70+ / month
Sentry developer:     $26 / month
Apple Dev Program:    $99 / year
Maintenance:          15–25% of initial build / year
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Year-one all-in for an indie React Native AI app: &lt;strong&gt;$45K–$78K&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison: scratch vs template
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Line item&lt;/th&gt;
&lt;th&gt;From scratch&lt;/th&gt;
&lt;th&gt;Production template&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Auth + Supabase + RLS&lt;/td&gt;
&lt;td&gt;40–80 hrs&lt;/td&gt;
&lt;td&gt;included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI provider wiring (BYOK)&lt;/td&gt;
&lt;td&gt;80–160 hrs&lt;/td&gt;
&lt;td&gt;included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20+ screens&lt;/td&gt;
&lt;td&gt;120–240 hrs&lt;/td&gt;
&lt;td&gt;included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stripe + license grant&lt;/td&gt;
&lt;td&gt;40–60 hrs&lt;/td&gt;
&lt;td&gt;included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Push notifications&lt;/td&gt;
&lt;td&gt;16–32 hrs&lt;/td&gt;
&lt;td&gt;included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EAS Build config&lt;/td&gt;
&lt;td&gt;8–16 hrs&lt;/td&gt;
&lt;td&gt;included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;One-time cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$32K–$62K&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$79&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Customization&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;20–60 hrs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time to App Store&lt;/td&gt;
&lt;td&gt;4–7 months&lt;/td&gt;
&lt;td&gt;2–6 weeks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The $79 isn't a discount on the engineering — it's the cost of buying a copy of architecture that already exists. If you want to see what a production template actually ships (RLS policies, BYOK AI wiring, Stripe license grants, 20+ screens), the &lt;a href="https://www.applighter.com/blog?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=honest-cost-react-native-ai-app-2026" rel="noopener noreferrer"&gt;Applighter blog&lt;/a&gt; has the full line-item breakdown and three real budget shapes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three real shapes, three real budgets
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Image → AI insight&lt;/strong&gt; (calorie scanner, plant ID, skincare): 380–620 hrs from scratch ($28K–$47K). Template: $79 + ~30 hrs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Voice → transcript + summary&lt;/strong&gt;: 480–740 hrs from scratch ($36K–$56K). Template: $79 + ~40 hrs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Document → chat (RAG)&lt;/strong&gt;: 520–820 hrs from scratch ($39K–$62K). Template: $79 + ~50 hrs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What free boilerplates miss
&lt;/h2&gt;

&lt;p&gt;A free Expo boilerplate gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Expo Router setup&lt;/li&gt;
&lt;li&gt;A login screen&lt;/li&gt;
&lt;li&gt;Maybe a tab bar&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A free boilerplate does &lt;em&gt;not&lt;/em&gt; give you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI provider abstraction (BYOK pattern)&lt;/li&gt;
&lt;li&gt;RLS policies + migrations&lt;/li&gt;
&lt;li&gt;Stripe webhook handlers with signature verification&lt;/li&gt;
&lt;li&gt;License grant logic&lt;/li&gt;
&lt;li&gt;20+ designer-vetted screens&lt;/li&gt;
&lt;li&gt;Push notification topic logic&lt;/li&gt;
&lt;li&gt;Accessibility audit&lt;/li&gt;
&lt;li&gt;Dark mode tested across every screen&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Paid UI kits from competitors like &lt;a href="https://reactnativebase.com" rel="nofollow noopener noreferrer"&gt;React Native Base&lt;/a&gt; ($99–$299) typically ship as &lt;em&gt;UI only&lt;/em&gt; — backend, AI, and licensing are still yours. That's where 70% of the hours live.&lt;/p&gt;

&lt;h2&gt;
  
  
  When build-from-scratch wins
&lt;/h2&gt;

&lt;p&gt;Three cases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Genuinely novel on-device AI (ExecuTorch, custom-trained vision)&lt;/li&gt;
&lt;li&gt;HIPAA / FedRAMP / on-prem constraints a template's RLS can't satisfy&lt;/li&gt;
&lt;li&gt;Existing in-house React Native team with idle capacity&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For everything else — solo indies, two-person teams, agencies shipping client demos fast — buy the architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: Do production AI templates include API keys?&lt;/strong&gt;&lt;br&gt;
A: The good ones don't — they use a BYOK pattern. Pick OpenAI, Anthropic, Whisper, Deepgram, AssemblyAI — your call. You pay the provider directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Year-one ongoing costs?&lt;/strong&gt;&lt;br&gt;
A: $3.6K–$30K. Mostly AI API spend. Supabase + EAS + Sentry are ~$2K/year combined.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Is React Native fast enough for streaming AI responses?&lt;/strong&gt;&lt;br&gt;
A: Yes. Reanimated v4 handles streaming text and audio waveforms at 60fps. See &lt;a href="https://reactnative.dev/docs/performance" rel="noopener noreferrer"&gt;React Native performance docs&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;What did your last React Native AI build actually cost — in hours or dollars? I'm collecting real numbers from indie devs in the comments, because the honest ones are almost impossible to find online.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>reactnative</category>
      <category>supabase</category>
      <category>expo</category>
    </item>
    <item>
      <title>React Native Performance Optimization — The 2026 Playbook</title>
      <dc:creator>Russel Dsouza</dc:creator>
      <pubDate>Tue, 23 Jun 2026 07:01:15 +0000</pubDate>
      <link>https://dev.to/russel_dsouza_bd584a3cb2a/react-native-performance-optimization-the-2026-playbook-2jai</link>
      <guid>https://dev.to/russel_dsouza_bd584a3cb2a/react-native-performance-optimization-the-2026-playbook-2jai</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs7bb72alov5aobykax1f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs7bb72alov5aobykax1f.png" alt=" " width="800" height="420"&gt;&lt;/a&gt; &lt;strong&gt;Measure on a Pixel 4a, not your iPhone 15 Pro.&lt;/strong&gt; Targets: cold start &amp;lt;2s, sustained scroll ≥58fps, tap-to-feedback &amp;lt;100ms, JS heap &amp;lt;180MB.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Turn on the New Architecture (Fabric + TurboModules + JSI).&lt;/strong&gt; ~40% cold start improvement, ~35% rendering speedup, ~25% memory drop. Every other optimization compounds on this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Default to &lt;code&gt;FlashList&lt;/code&gt;, not &lt;code&gt;FlatList&lt;/code&gt;.&lt;/strong&gt; ~10× scroll throughput via row recycling. Set &lt;code&gt;estimatedItemSize&lt;/code&gt; close to median row height and you're done.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Animations on the UI thread, not JS.&lt;/strong&gt; &lt;code&gt;useNativeDriver: true&lt;/code&gt; for &lt;code&gt;Animated&lt;/code&gt;. Reanimated 3 with worklets for anything gesture-driven.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit re-renders.&lt;/strong&gt; React DevTools → "highlight updates when rendering." Most "RN is slow" complaints are 5× re-render counts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trim cold start.&lt;/strong&gt; &lt;code&gt;react-native-bundle-visualizer&lt;/code&gt; + lazy-load non-first-frame screens. Defer analytics/remote config via &lt;code&gt;InteractionManager.runAfterInteractions&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;I read a React Native performance post the other week that opened with a long argument about whether &lt;code&gt;useMemo&lt;/code&gt; was overused. The post was 2,200 words. It didn't mention the New Architecture once.&lt;/p&gt;

&lt;p&gt;That's the state of most React Native advice you'll find in 2026. The framework has changed more in the last eighteen months than it did in the previous five years — and a lot of the writing about it hasn't caught up. So here's what I'd actually tell a team that wants their app to feel native, in the order I'd tell it.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Stop optimizing. Measure.
&lt;/h2&gt;

&lt;p&gt;Almost every team I've worked with that complained about React Native performance had never sat down with a real low-end Android and a profiler open. They had vibes. The vibes said the app was slow. The profiler usually said the app was rendering forty-seven times when it should have rendered three. That's not a framework problem. That's a render hygiene problem, and you can't fix it until you can see it.&lt;/p&gt;

&lt;p&gt;Here are the numbers I benchmark against. Not on the iPhone 15 Pro sitting on my desk — on a Pixel 4a, the device my actual user is holding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cold start                  &amp;lt; 2 seconds
Sustained scroll            &amp;gt;= 58 fps
Tap to first visual feedback &amp;lt; 100ms
JavaScript heap             &amp;lt; 180 MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If those numbers don't mean anything to you yet, that's fine. They will after a week of measuring.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Turn on the New Architecture
&lt;/h2&gt;

&lt;p&gt;I don't think this gets enough airtime. The New Architecture — &lt;strong&gt;Fabric, TurboModules, JSI&lt;/strong&gt; — is the foundation everything else compounds on. Teams that migrate report:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;~40% cold start improvement&lt;/li&gt;
&lt;li&gt;~35% rendering speedup&lt;/li&gt;
&lt;li&gt;~25% memory drop&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The reason isn't magic. The old React Native bridge serialized every JavaScript-to-native call as JSON. It was slow on purpose, because being asynchronous and serialized was the easiest way to keep the threads sane. JSI replaced that with a direct C++ function call. Synchronous. No serialization. &lt;strong&gt;Latency dropped by ~40× on hot paths.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can't really optimize on top of the old bridge anymore. Every other thing in this post assumes you're on the New Architecture. If you're not, that's your only homework.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Lists are where the complaints come from
&lt;/h2&gt;

&lt;p&gt;Lists are where almost every React Native performance complaint comes from in production. Long feeds. Chat histories. Galleries. Anything that scrolls. The default React Native &lt;code&gt;FlatList&lt;/code&gt; is actually pretty conservative — it doesn't know how tall your rows are, it can't recycle views, and it re-renders eagerly on data changes.&lt;/p&gt;

&lt;p&gt;The fix is &lt;code&gt;FlashList&lt;/code&gt; from Shopify, which gives you roughly &lt;strong&gt;10× the throughput&lt;/strong&gt; by recycling row views instead of mounting and unmounting them. The API is nearly drop-in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FlashList&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@shopify/flash-list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;FlashList&lt;/span&gt;
  &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;renderItem&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{({&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Row&lt;/span&gt; &lt;span class="na"&gt;item&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;estimatedItemSize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;// &amp;lt;-- this is the prop that matters&lt;/span&gt;
  &lt;span class="nx"&gt;keyExtractor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The one prop that matters more than any other is &lt;code&gt;estimatedItemSize&lt;/code&gt;. Get it close to your median row height and the rest takes care of itself.&lt;/p&gt;

&lt;p&gt;There are cases where &lt;code&gt;FlatList&lt;/code&gt; still wins:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Short lists under ~20 items, where &lt;code&gt;FlashList&lt;/code&gt;'s recycler is overhead you can't recoup&lt;/li&gt;
&lt;li&gt;Wildly heterogeneous content where recycling falls apart because no two rows share a layout&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are edge cases. Default to &lt;code&gt;FlashList&lt;/code&gt;. You will be surprised how much of your perceived "React Native is slow" feeling is actually a &lt;code&gt;FlatList&lt;/code&gt; you should have upgraded twelve months ago.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Re-renders are where most developers eventually live
&lt;/h2&gt;

&lt;p&gt;Unnecessary re-renders are the single most underestimated React Native performance problem. A component that renders five times when it should render once is &lt;strong&gt;5× the JavaScript thread work&lt;/strong&gt;, and the JavaScript thread is still where almost every user-perceived jank comes from.&lt;/p&gt;

&lt;p&gt;Open React DevTools, turn on "highlight updates when rendering," and scroll through your app. If you see things flashing that have no visual change, you have a problem.&lt;/p&gt;

&lt;p&gt;The fixes are mundane:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Memoize leaf components that re-render with their parents&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// useCallback for handlers passed to memoized children&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onPress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/* ... */&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;

&lt;span class="c1"&gt;// Don't memoize primitives — net loss&lt;/span&gt;
&lt;span class="c1"&gt;// const memoizedNumber = useMemo(() =&amp;gt; 42, [])  // pointless&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Co-locate state, push it down toward where it's actually used, and reach for Zustand or Jotai before context-induced cascades start eating your frames. None of it is glamorous. All of it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Animations belong on the UI thread
&lt;/h2&gt;

&lt;p&gt;If your animation is running on the JavaScript thread, it is going to drop frames. Not might. &lt;strong&gt;Will.&lt;/strong&gt; The first time a network request resolves while a sheet is sliding, the animation will judder. There's no escaping this with cleverness. The fix is to get animations off the JS thread entirely.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;Animated&lt;/code&gt;, that means &lt;code&gt;useNativeDriver: true&lt;/code&gt; on every property that supports it (transforms and opacity, in practice):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Animated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;translateY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;toValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;useNativeDriver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// &amp;lt;-- non-negotiable&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For anything gesture-driven, it means Reanimated 3 with worklets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useSharedValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useAnimatedStyle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;withSpring&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-native-reanimated&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSharedValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;animatedStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAnimatedStyle&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;translateX&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;withSpring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;

&lt;span class="c1"&gt;// runs on the UI thread via JSI — survives a blocking JS reducer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Worklets run on the UI thread, share memory with native via JSI, and survive blocking JavaScript work without missing a beat. Pair them with &lt;code&gt;react-native-gesture-handler&lt;/code&gt; for pan and swipe interactions — the legacy &lt;code&gt;PanResponder&lt;/code&gt; still routes through the JS thread even on the New Architecture.&lt;/p&gt;

&lt;p&gt;The thing about Reanimated isn't that it's faster than &lt;code&gt;Animated&lt;/code&gt;. It's that it &lt;strong&gt;decouples animation work from your JS thread entirely&lt;/strong&gt;. That's a different kind of fast. The kind that survives a poorly-written reducer.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Cold start is the first impression
&lt;/h2&gt;

&lt;p&gt;Cold start is the user's first impression of your app every single morning, and almost every team I've audited has a cold start they could cut in half mechanically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Audit your bundle&lt;/span&gt;
npx react-native-bundle-visualizer

&lt;span class="c"&gt;# Look at the ten largest modules — they'll explain ~60% of your bundle&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The usual suspects: date libraries, icon sets, Lottie animations, heavy localization packages. Lazy-load screens that aren't on the first frame:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// React.lazy + dynamic import inside the navigator&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;HomeTab&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lazy&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./screens/HomeTab&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SettingsTab&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lazy&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./screens/SettingsTab&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The login screen should never be pulling the home tab's bundle.&lt;/p&gt;

&lt;p&gt;And — this is the one teams forget — defer non-critical setup past first paint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;InteractionManager&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-native&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nx"&gt;InteractionManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runAfterInteractions&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;initAnalytics&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nf"&gt;bootstrapRemoteConfig&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nf"&gt;maybePromptForReview&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;None of this code needs to run before your user sees the home screen. &lt;strong&gt;Treat the first frame as sacred.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I've left out (and why it's second-order)
&lt;/h2&gt;

&lt;p&gt;I've left a few things out — image caching with &lt;code&gt;expo-image&lt;/code&gt;, memory leaks from uncleaned subscriptions, the case for and against writing TurboModules. They matter, but they're second-order.&lt;/p&gt;

&lt;p&gt;If you turn on the New Architecture, move lists to &lt;code&gt;FlashList&lt;/code&gt;, get animations on the UI thread, audit re-renders, and trim your bundle, you will have done &lt;strong&gt;80% of the work that matters&lt;/strong&gt;. The rest is housekeeping.&lt;/p&gt;

&lt;p&gt;The teams I see ship fast React Native apps in 2026 don't have secret tricks. They have budgets. They measure. They refuse to ship a regression. That's the whole game.&lt;/p&gt;




&lt;p&gt;If you want a project scaffold that already ships with these defaults — New Architecture on, &lt;code&gt;FlashList&lt;/code&gt; by default, Reanimated 3 for animations, &lt;code&gt;expo-image&lt;/code&gt; for remote images — that's what &lt;a href="https://www.rapidnative.com/blogs/react-native-performance-optimization-2026-playbook?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=react-native-performance-2026" rel="noopener noreferrer"&gt;the RapidNative team has been building&lt;/a&gt;. The canonical post on the RapidNative blog has the same playbook plus the specific defaults the generator ships with.&lt;/p&gt;




&lt;p&gt;What's the single optimization that bought you the most measurable wins on your last React Native app? Drop your before/after numbers in the comments — I'm collecting the patterns that work in production for a follow-up.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>performance</category>
      <category>mobile</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Stop Reinventing Auth in React Native</title>
      <dc:creator>Russel Dsouza</dc:creator>
      <pubDate>Mon, 22 Jun 2026 06:08:55 +0000</pubDate>
      <link>https://dev.to/russel_dsouza_bd584a3cb2a/stop-reinventing-auth-in-react-native-2in2</link>
      <guid>https://dev.to/russel_dsouza_bd584a3cb2a/stop-reinventing-auth-in-react-native-2in2</guid>
      <description>&lt;ul&gt;
&lt;li&gt;Auth is a commodity surface in 2026. Don't rebuild it.&lt;/li&gt;
&lt;li&gt;60–120 hours of senior-dev work for $59–$299 of template = obvious trade.&lt;/li&gt;
&lt;li&gt;Vet templates for: &lt;strong&gt;RLS in versioned migrations&lt;/strong&gt;, &lt;strong&gt;separated Supabase clients&lt;/strong&gt; (anon vs. service_role), a &lt;strong&gt;working Stripe webhook&lt;/strong&gt;, and &lt;strong&gt;≤1k lines of auth code&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Roll your own only if you're regulated, weird, or learning.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;You sat down to ship a side project. You opened a fresh Expo app. You typed "weekend plan: build auth."&lt;/p&gt;

&lt;p&gt;Three weeks later you're still here.&lt;/p&gt;

&lt;p&gt;This post is the argument for &lt;code&gt;git clone&lt;/code&gt;ing somebody else's auth and getting back to writing the thing that's actually your app.&lt;/p&gt;

&lt;h2&gt;
  
  
  The full auth surface in React Native (2026)
&lt;/h2&gt;

&lt;p&gt;It's never just a login screen. Real auth for a mobile app shipping on the App Store today includes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- Email/password (optional, but users ask)
- Magic links (token hashing, single-use, expiry, rate limits)
- Google OAuth (iOS + Android URL schemes)
- Apple Sign-In (mandatory if you ship Google on iOS)
- Session persistence + refresh
- Deep links for verification emails
- RLS on every user-touching table
- Account deletion (App Store rule)
- Password reset
- Tests for at least magic link + OAuth happy paths
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A focused senior dev spends 60–120 hours building this correctly. At $120/hr that's $7K–$14K of opportunity cost. And the failure mode for skipping a step isn't "ugly UI" — it's a leaked credential.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a good template gives you
&lt;/h2&gt;

&lt;p&gt;Skip ahead. The stack you want is roughly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NextAuth&lt;/strong&gt; for the session orchestration (provider-agnostic)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supabase&lt;/strong&gt; for the database, with RLS turned on&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A magic-link endpoint&lt;/strong&gt; with hashed tokens&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Stripe webhook&lt;/strong&gt; that converts checkout → license grant&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two Supabase clients&lt;/strong&gt;: anon for the app, service role for trusted server code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's what the magic-link route looks like in a real template (&lt;code&gt;app/api/auth/magic-link/route.ts&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Pseudocode showing the shape, not the literal source&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tokenHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;rateLimit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;window&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;magic_link_tokens&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;token_hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tokenHash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;expires_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendEmail&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/verify?token=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three details to notice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The raw token never touches the database. Only the SHA-256 hash is stored.&lt;/li&gt;
&lt;li&gt;There's a rate limit (3 requests / 60s per email).&lt;/li&gt;
&lt;li&gt;The expiry is 15 minutes, not "we'll check it later."&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If your homegrown magic-link route is missing any one of those, you've already shipped a vulnerability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Supabase clients, not one
&lt;/h2&gt;

&lt;p&gt;The single most common security mistake in React Native + Supabase code is one shared client. Don't.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/db/supabaseClient.ts — browser/app code&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ANON_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// modules/db/supabaseServer.ts — server-only, never imported from app/&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;supabaseAdmin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SERVICE_ROLE_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;service_role&lt;/code&gt; key bypasses RLS. If it appears in any file that gets bundled into your client (anything under your Expo &lt;code&gt;app/&lt;/code&gt;), you've shipped the keys to your entire database. A good template separates these two clients structurally so the bad import is visibly wrong in code review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build vs. buy
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;Roll your own&lt;/th&gt;
&lt;th&gt;Auth-as-a-service&lt;/th&gt;
&lt;th&gt;Template&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Magic link&lt;/td&gt;
&lt;td&gt;Build&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google + Apple OAuth&lt;/td&gt;
&lt;td&gt;1–2d each&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RLS policies&lt;/td&gt;
&lt;td&gt;Hand-written&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Versioned migrations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stripe license grants&lt;/td&gt;
&lt;td&gt;3–5d&lt;/td&gt;
&lt;td&gt;DIY&lt;/td&gt;
&lt;td&gt;Wired&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Token hashing + rate limits&lt;/td&gt;
&lt;td&gt;DIY&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source you own&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recurring cost&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$25–$500+/mo&lt;/td&gt;
&lt;td&gt;One-time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time to first signed-in user&lt;/td&gt;
&lt;td&gt;2–6 weeks&lt;/td&gt;
&lt;td&gt;1–3 days&lt;/td&gt;
&lt;td&gt;1 day&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Auth-as-a-service (Clerk, Auth0) is fine if your buyer is an enterprise security team and your pricing supports it. For an indie consumer app it's almost always overkill and locks you into someone's opinions about identity.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you SHOULD roll your own
&lt;/h2&gt;

&lt;p&gt;There are real cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Regulated industries (healthcare, fintech) where security needs a small bespoke surface to audit&lt;/li&gt;
&lt;li&gt;Strange tenancy models (per-device identity, supervised iPad fleets)&lt;/li&gt;
&lt;li&gt;You're learning OAuth/JWT/RLS and the project IS the learning&lt;/li&gt;
&lt;li&gt;You have an internal IdP to integrate with&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For everyone else: don't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vetting a template before you buy
&lt;/h2&gt;

&lt;p&gt;Most templates are zombies. Use this checklist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;[ ] Auth code is under ~1,000 lines
[ ] RLS policies live in versioned migrations, not "enable it in the dashboard"
[ ] Stripe webhook writes to the same DB as auth
[ ] Anon and service_role clients are in separate files
[ ] Dependencies updated in the last 90 days
[ ] You can read the magic-link route in one sitting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a template passes those six, you're buying real engineering, not a UI kit with a database attached.&lt;/p&gt;




&lt;p&gt;If you want to see the actual files — &lt;code&gt;lib/auth.ts&lt;/code&gt;, &lt;code&gt;app/api/auth/magic-link/route.ts&lt;/code&gt;, &lt;code&gt;app/api/webhook/stripe/route.ts&lt;/code&gt; — the longer breakdown lives on &lt;a href="https://www.applighter.com/blog?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=stop-reinventing-auth-buy-a-react-native-template" rel="noopener noreferrer"&gt;the Applighter blog&lt;/a&gt;, including a deeper walkthrough of how the Stripe webhook binds a checkout to a Supabase identity row.&lt;/p&gt;

&lt;p&gt;Further reading:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.expo.dev/develop/authentication/" rel="noopener noreferrer"&gt;Expo authentication docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://supabase.com/docs/guides/auth" rel="noopener noreferrer"&gt;Supabase Auth docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html" rel="noopener noreferrer"&gt;OWASP Authentication Cheat Sheet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What was the auth gotcha that ate the most time on your last project? Drop it in the comments — I'm collecting the patterns for a follow-up on the failures that don't make it into the documentation.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>expo</category>
      <category>supabase</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Cross-Platform vs Native: The 2026 Debate Is Over</title>
      <dc:creator>Russel Dsouza</dc:creator>
      <pubDate>Tue, 16 Jun 2026 14:14:06 +0000</pubDate>
      <link>https://dev.to/russel_dsouza_bd584a3cb2a/cross-platform-vs-native-the-2026-debate-is-over-dm8</link>
      <guid>https://dev.to/russel_dsouza_bd584a3cb2a/cross-platform-vs-native-the-2026-debate-is-over-dm8</guid>
      <description>&lt;p&gt;If you're still picking native iOS + native Android for a new app in 2026, you're paying a tax that 95% of teams have stopped paying. This isn't a "both sides have merit" post. It's a position: cross-platform won. Here's the evidence.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;For ~95% of apps shipped in 2026, cross-platform won.&lt;/strong&gt; React Native and Flutter together cover &amp;gt;80% of new cross-platform work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The performance gap collapsed.&lt;/strong&gt; Hermes + the New Architecture (React Native) and Impeller (Flutter) closed it. The 60fps ceiling is now the floor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The economics were never close.&lt;/strong&gt; 30–80% cheaper, 30–40% faster to ship — and feature parity is the default, not a goal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native still wins in ~5% of cases.&lt;/strong&gt; High-end games, heavy AR/VR, sub-16ms ML on camera buffers, deep OS extensions as the primary surface, specific compliance regimes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The real debate now isn't React Native vs Swift.&lt;/strong&gt; It's how much of your codebase a human writes vs. an AI generates.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The companies you use every day already voted
&lt;/h2&gt;

&lt;p&gt;The clearest signal isn't a benchmark. It's what large engineering orgs — the ones that &lt;em&gt;could&lt;/em&gt; afford to maintain two native teams — actually committed to.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Company&lt;/th&gt;
&lt;th&gt;Stack&lt;/th&gt;
&lt;th&gt;Code-share&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Discord&lt;/td&gt;
&lt;td&gt;React Native&lt;/td&gt;
&lt;td&gt;98% across iOS/Android&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shopify&lt;/td&gt;
&lt;td&gt;React Native&lt;/td&gt;
&lt;td&gt;~80% shared across mobile surfaces&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Microsoft (Teams, Office surfaces)&lt;/td&gt;
&lt;td&gt;React Native&lt;/td&gt;
&lt;td&gt;Significant shared layers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pinterest&lt;/td&gt;
&lt;td&gt;React Native&lt;/td&gt;
&lt;td&gt;Core feature surfaces&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Coinbase&lt;/td&gt;
&lt;td&gt;React Native&lt;/td&gt;
&lt;td&gt;Migrated from native&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Pay&lt;/td&gt;
&lt;td&gt;Flutter&lt;/td&gt;
&lt;td&gt;Rewritten cross-platform&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BMW My BMW app&lt;/td&gt;
&lt;td&gt;Flutter&lt;/td&gt;
&lt;td&gt;Cross-platform from launch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ByteDance (multiple apps)&lt;/td&gt;
&lt;td&gt;Flutter&lt;/td&gt;
&lt;td&gt;Production at scale&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When Discord ships 98% shared code to hundreds of millions of users — Pixel 3 to iPhone 16 Pro — the "but cross-platform can't handle real production scale" argument stopped being an argument. It became a sentence you say to avoid making a decision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2026 market share:&lt;/strong&gt; Flutter ~46%, React Native ~35%, together &amp;gt;80% of new cross-platform work.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "modern cross-platform" actually means
&lt;/h2&gt;

&lt;p&gt;This part trips up everyone working from a 2018 mental model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Modern React Native&lt;/strong&gt; renders to real &lt;code&gt;UIView&lt;/code&gt; and &lt;code&gt;android.view.View&lt;/code&gt; instances. With the New Architecture (JSI + Fabric + TurboModules), the asynchronous JS-to-native bridge is gone. UI renders synchronously where it matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Modern Flutter&lt;/strong&gt; draws every pixel via Skia (and now Impeller), pre-compiling shaders to eliminate first-frame jank.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Neither&lt;/strong&gt; ships WebViews. &lt;strong&gt;Neither&lt;/strong&gt; ships "almost native" experiences. The binaries go through the App Store and Google Play exactly like a Swift or Kotlin app. Your QA team can't tell the difference; neither can the user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// What a 2026 React Native component actually compiles to:&lt;/span&gt;
&lt;span class="c1"&gt;// On iOS: a real UIView in the view hierarchy.&lt;/span&gt;
&lt;span class="c1"&gt;// On Android: a real android.view.View.&lt;/span&gt;
&lt;span class="c1"&gt;// No WebView. No bridge. No simulated-native abstraction.&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;View&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Pressable&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-native&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onPress&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;onPress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Pressable&lt;/span&gt; &lt;span class="nx"&gt;onPress&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onPress&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;View&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Text&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Text&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/View&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Pressable&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;Pressable&lt;/code&gt; is a real native gesture recognizer. That &lt;code&gt;View&lt;/code&gt; is a real &lt;code&gt;UIView&lt;/code&gt;. No wrapper, no shim.&lt;/p&gt;

&lt;h2&gt;
  
  
  The performance gap collapsed (three reasons)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Hermes + the New Architecture.&lt;/strong&gt; Meta's JS engine built for React Native cut startup time roughly in half vs. the old JSC stack. JSI removed the bridge bottleneck for animations and scrolling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Flutter's Impeller renderer.&lt;/strong&gt; Pre-compiles shaders, eliminates the first-frame jank that defined cross-platform animation in 2019.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Hardware caught up.&lt;/strong&gt; A baseline 2026 Android device has more CPU/GPU than an iPhone 7. The frame budget that used to be tight on cross-platform is now wide enough that unoptimized code clears 60fps.&lt;/p&gt;

&lt;p&gt;For feeds, forms, e-commerce, fintech, social, dashboards, productivity, content apps, marketplaces, on-demand — there is no user-detectable performance gap. "But native is faster" became technically true and operationally irrelevant.&lt;/p&gt;

&lt;h2&gt;
  
  
  The economics were never close
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Two codebases (Swift + Kotlin)
Two teams of senior engineers (both scarce, both expensive)
Two design system implementations
Two CI pipelines
Two release coordinations per shipped feature
Two sets of "works on iOS but not Android" bugs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Industry estimates: native dual-platform is &lt;strong&gt;30–80% more expensive and 30–40% slower&lt;/strong&gt; than cross-platform.&lt;/p&gt;

&lt;p&gt;But cost isn't the main thing. The main thing is &lt;strong&gt;shipping the same feature to both platforms on the same day&lt;/strong&gt;. The number of native teams that have ever maintained true feature parity rounds to zero. Cross-platform makes parity the default.&lt;/p&gt;

&lt;h2&gt;
  
  
  When native still wins (the 5%)
&lt;/h2&gt;

&lt;p&gt;Be honest about which bucket you're in. Native is still the right call if:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;High-end games.&lt;/strong&gt; 3D rendering, 120Hz determinism. Use Unity, Unreal, or native Metal/Vulkan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Heavy AR/VR.&lt;/strong&gt; Room-scale tracking, persistent anchors, custom shaders. Wrappers exist; they lag Apple/Google by months.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sub-16ms on-device ML.&lt;/strong&gt; Real-time barcode at scale, pose estimation, on-device transcription on the camera buffer. Native gets you direct Core ML / NNAPI without marshalling overhead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OS extensions as the primary surface.&lt;/strong&gt; CarPlay, Wear OS tiles, Live Activities, App Clips, system widgets, Siri intents. Cross-platform apps regularly ship native modules for these — but if your &lt;em&gt;core product&lt;/em&gt; lives there, native-first is honest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Specific compliance regimes.&lt;/strong&gt; FIPS 140-3, certain MDM and keystore behaviors, specific healthcare or fintech certifications. Shrinking, but real.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If your app doesn't sit cleanly in one of those buckets, native-by-default isn't the safe choice anymore. It's the expensive choice you're making out of habit.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 60-second decision framework
&lt;/h2&gt;

&lt;p&gt;Three questions, in order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Does your core experience need direct hardware
   access at native latency?
   → YES: native. Stop here.
   → NO: continue.

2. Do you already have two full native teams in seat,
   budget locked, indefinitely?
   → YES: keep using them; switching has migration cost.
   → NO: continue.

3. Do users care more about features and shipping speed
   than which engine renders the button?
   → YES (this is ~95% of apps): cross-platform.
       Pick React Native or Flutter based on team preference.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Most teams answer it in 60 seconds and the answer is cross-platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  The new debate that replaced the old one
&lt;/h2&gt;

&lt;p&gt;Here's the part most "cross-platform vs native" posts haven't caught up to.&lt;/p&gt;

&lt;p&gt;Once cross-platform won (and it has), &lt;strong&gt;one codebase becomes a target small enough for an AI to generate, modify, and maintain in real time.&lt;/strong&gt; That's structurally not true of two native codebases — generating coherent, synchronized Swift and Kotlin for the same feature, with matching design systems and parallel state, is an order of magnitude harder.&lt;/p&gt;

&lt;p&gt;The frameworks that won the cross-platform debate are also the frameworks AI generation works well on. That's not a coincidence. It's why this debate ended &lt;em&gt;now&lt;/em&gt;, not five years ago.&lt;/p&gt;

&lt;p&gt;Tools like &lt;a href="https://www.rapidnative.com/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=cross-platform-vs-native-debate-is-over" rel="noopener noreferrer"&gt;RapidNative&lt;/a&gt; generate real React Native + Expo code from a prompt — TypeScript output, exportable, ships to both stores. If you tried this with Swift + Kotlin in parallel and kept them in sync, you'd still be writing prompts in 2030.&lt;/p&gt;

&lt;p&gt;The real 2026 question isn't React Native vs Swift. It's &lt;em&gt;how much of your cross-platform codebase a human writes vs. an AI generates&lt;/em&gt;. That's the conversation worth having.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick objection-handling
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;"Will my cross-platform app get rejected from the App Store?"&lt;/strong&gt; No. Discord, Shopify, BMW, Coinbase, and Google Pay don't get special treatment. They ship through the same review process you would. The "Apple secretly favors native" story is residue from the WebView-era rejections of the early 2010s. Modern cross-platform doesn't use WebViews.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Is React Native dying? Is Flutter slowing down?"&lt;/strong&gt; Both healthier than ever. React Native shipped the New Architecture (now default). Expo's tooling around it makes setup a single command. Flutter continues shipping quarterly stable releases with Impeller, expanded web/desktop, improved Material/Cupertino. Pick whichever your team prefers writing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"What about heavy animation?"&lt;/strong&gt; For 95% of animation work — list scrolls, modal transitions, parallax, shared element transitions, gesture-driven interactions — both frameworks run at 60–120fps indistinguishably from native. If you're animating 10,000+ particles or a fluid simulation, that's the edge case where native or a game engine wins. For everything else, the gap is invisible.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bottom line
&lt;/h2&gt;

&lt;p&gt;Cross-platform vs native isn't over because cross-platform got perfect. It's over because the gap between "perfect" and "good enough for what 95% of apps actually need" stopped being a gap at all.&lt;/p&gt;

&lt;p&gt;Discord, Shopify, Microsoft, Pinterest, Coinbase, Google Pay, BMW, ByteDance didn't pick cross-platform out of laziness. They picked it because the math, the talent market, the user experience, and the platform tooling all pointed the same direction.&lt;/p&gt;

&lt;p&gt;If you're in the 5% where native belongs, you already know. If you're not — and most of you aren't — pick a cross-platform stack and ship.&lt;/p&gt;




&lt;p&gt;What's still keeping your team on native in 2026? Genuinely curious — drop the use case in the comments. I'm collecting examples of where the 5% actually shows up in real projects.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>flutter</category>
      <category>mobile</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
