<?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: Chris F A</title>
    <description>The latest articles on DEV Community by Chris F A (@chris_fa_8fca9f4ba09d963).</description>
    <link>https://dev.to/chris_fa_8fca9f4ba09d963</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%2F4020934%2Fcc01d7ef-aaf5-4fb8-94fd-d0a6a1a5aba3.png</url>
      <title>DEV Community: Chris F A</title>
      <link>https://dev.to/chris_fa_8fca9f4ba09d963</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chris_fa_8fca9f4ba09d963"/>
    <language>en</language>
    <item>
      <title>Designing a build artifact system a 3-person team can actually navigate</title>
      <dc:creator>Chris F A</dc:creator>
      <pubDate>Wed, 29 Jul 2026 10:27:27 +0000</pubDate>
      <link>https://dev.to/chris_fa_8fca9f4ba09d963/designing-a-build-artifact-system-a-3-person-team-can-actually-navigate-32gi</link>
      <guid>https://dev.to/chris_fa_8fca9f4ba09d963/designing-a-build-artifact-system-a-3-person-team-can-actually-navigate-32gi</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Filenames can't carry metadata. Stop asking them to — push the data into the binary and the EAS build record instead.&lt;/li&gt;
&lt;li&gt;Every build needs 5 attributes: git SHA, environment, version + build number, who triggered it, and when.&lt;/li&gt;
&lt;li&gt;One convention that holds up: &lt;code&gt;&amp;lt;product&amp;gt;-&amp;lt;env&amp;gt;-v&amp;lt;semver&amp;gt;-b&amp;lt;buildnum&amp;gt;-&amp;lt;sha7&amp;gt;.&amp;lt;ext&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;appVersionSource: remote&lt;/code&gt; + &lt;code&gt;autoIncrement: true&lt;/code&gt; means you never ship two builds numbered 47.&lt;/li&gt;
&lt;li&gt;Release notes generate themselves from &lt;code&gt;git log&lt;/code&gt; between two SHAs — one source, three surfaces.&lt;/li&gt;
&lt;li&gt;A Notion database with 6 columns beat Sheets, Airtable, Linear, and a &lt;code&gt;#builds&lt;/code&gt; Slack channel.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;By month four of any real mobile project, you have ~400 build artifacts scattered across EAS Build history, Slack DMs ("here's the beta build.ipa"), Google Drive folders titled &lt;code&gt;builds temp DELETE LATER&lt;/code&gt;, and someone's Downloads folder. When QA asks "which build did we send to the pilot testers on Tuesday?", nobody knows. Not the PM, not the tech lead, not the person who literally sent it.&lt;/p&gt;

&lt;p&gt;This is a workflow problem, not a tools problem. Every team I've watched solve it did four specific things. Every team still drowning skipped one of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Finder problem: 400 artifacts, no naming convention
&lt;/h2&gt;

&lt;p&gt;Here's a real Finder window from a team I audited last month:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MyApp-build.ipa
MyApp-build (1).ipa
MyApp-build-final.ipa
MyApp-build-final-USE-THIS.ipa
MyApp-preview-v3.ipa
MyApp-preview-v3-hotfix.ipa
app-release.aab
app-release (2).aab
MyApp_2026-06-14.ipa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;None of these tell you: what commit, what env, what tester group, what release date, what build number ASC saw. To answer any of those, you have to open the EAS dashboard, cross-reference build ID, cross-reference commit SHA, and hope nobody force-pushed to the branch since.&lt;/p&gt;

&lt;p&gt;The root cause is that filesystem filenames are being asked to carry metadata they can't hold. The fix is to stop trying and move the metadata upstream — to the build itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 5 attributes every build must carry (and where to attach them)
&lt;/h2&gt;

&lt;p&gt;Every artifact needs to answer:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;What code?&lt;/strong&gt; — git SHA (short, 7 chars) + branch&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What environment?&lt;/strong&gt; — dev / preview / production / hotfix&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What version?&lt;/strong&gt; — semantic version + build number (they're different things)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Who / what triggered it?&lt;/strong&gt; — human name or CI job ID&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When?&lt;/strong&gt; — timestamp in one timezone (pick one, stick with it — IST for us)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Where to attach them, in order of durability:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;In the binary itself&lt;/strong&gt; (Info.plist / AndroidManifest) — survives everything, hardest to fake&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In the EAS build metadata&lt;/strong&gt; (&lt;code&gt;gitCommitHash&lt;/code&gt;, &lt;code&gt;gitCommitMessage&lt;/code&gt; — EAS captures these automatically if you use &lt;code&gt;appVersionSource: remote&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In the artifact filename&lt;/strong&gt; when downloaded — mostly for humans skimming Finder&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In your team's tracker&lt;/strong&gt; (Notion / Linear / Slack canvas) — the human-readable index&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The mistake teams make: they only do #3 (filename) and skip #1, #2, #4. Filename gets truncated, renamed, forwarded — the data dies. Metadata in the binary lives forever.&lt;/p&gt;

&lt;p&gt;Here's the EAS-side setup that makes #1 and #2 automatic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cli"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"appVersionSource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"remote"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"production"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"autoIncrement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"APP_ENV"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"production"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;appVersionSource: remote&lt;/code&gt; tells EAS to manage build numbers server-side, so you never manually bump &lt;code&gt;ios.buildNumber&lt;/code&gt; again and you never ship two builds with number 47. &lt;code&gt;autoIncrement: true&lt;/code&gt; adds one on every build. &lt;code&gt;APP_ENV&lt;/code&gt; gets baked into the binary and is readable at runtime — so the app can display its own env in a debug menu, which is worth an hour of QA time per week alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  A naming + tagging scheme that survives 6 months
&lt;/h2&gt;

&lt;p&gt;For the human-facing name (filenames, Slack shares, Notion titles), one convention that's held up across three teams:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;product&amp;gt;-&amp;lt;env&amp;gt;-v&amp;lt;semver&amp;gt;-b&amp;lt;buildnum&amp;gt;-&amp;lt;sha7&amp;gt;.&amp;lt;ext&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rapidnative-preview-v2.4.1-b127-a3f9c81.ipa
rapidnative-production-v2.4.0-b125-d7e21bc.aab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rules that make this work in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No spaces, no parens, no "final", no "USE-THIS".&lt;/strong&gt; Ever.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Env is one of exactly 4 values.&lt;/strong&gt; dev / preview / production / hotfix. If you need a fifth, you don't — merge it into one of these.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SHA is always 7 chars.&lt;/strong&gt; Not 6, not 8. Consistency matters more than length.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The tracker row uses the same string as the row's title.&lt;/strong&gt; So Cmd-F in Notion finds the artifact instantly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is boring on purpose. Boring conventions are the ones that survive team turnover.&lt;/p&gt;

&lt;h2&gt;
  
  
  Release notes that write themselves from artifact metadata
&lt;/h2&gt;

&lt;p&gt;Once every build carries git SHA + previous-build's SHA (EAS knows both), release notes generate themselves. A tiny GitHub Actions step that runs after &lt;code&gt;eas build&lt;/code&gt; completes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generate release notes&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;PREV_SHA=$(gh api repos/${{ github.repository }}/releases/latest --jq .tag_name || echo "HEAD~10")&lt;/span&gt;
    &lt;span class="s"&gt;CUR_SHA=${{ github.sha }}&lt;/span&gt;
    &lt;span class="s"&gt;echo "## Changes in build $CUR_SHA" &amp;gt; release-notes.md&lt;/span&gt;
    &lt;span class="s"&gt;git log --pretty=format:"- %s (%an)" "$PREV_SHA..$CUR_SHA" &amp;gt;&amp;gt; release-notes.md&lt;/span&gt;
    &lt;span class="s"&gt;echo "" &amp;gt;&amp;gt; release-notes.md&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output becomes the &lt;code&gt;testInformation&lt;/code&gt; field when you &lt;code&gt;eas submit&lt;/code&gt; to TestFlight (external testers see it) and doubles as the Slack post when the build lands. One source, three surfaces, zero manual writing. If you'd rather not maintain the workflow file yourself, &lt;a href="https://www.letsdeploy.it/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=build-artifact-system-small-teams" rel="noopener noreferrer"&gt;letsdeploy.it&lt;/a&gt; ships this exact step — SHA diffing, note generation, submission — as part of the pipeline.&lt;/p&gt;

&lt;p&gt;The design principle: &lt;strong&gt;any artifact metadata worth showing to a human should surface in at least two places automatically.&lt;/strong&gt; If it lives in only one place, it will get lost or become stale.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one Notion / Linear pattern that beat every other tool
&lt;/h2&gt;

&lt;p&gt;I tried five variants of build tracking over two years: Google Sheets, Airtable, a Notion database, a Linear project view, and a &lt;code&gt;#builds&lt;/code&gt; Slack channel.&lt;/p&gt;

&lt;p&gt;The one that stuck is embarrassingly simple: a &lt;strong&gt;Notion database with 6 columns and a filter&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Columns: Artifact name (the string above), Env, Version, SHA, Triggered by, Status. Filter: Env = production view for stakeholders, Env = preview view for QA, Status = failed view for post-mortems. That's it. No custom formulas, no linked databases, no rollups.&lt;/p&gt;

&lt;p&gt;Why it beat the others:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sheets&lt;/strong&gt; — everyone can edit, so schema drifts. Someone adds a "Notes" column, someone else adds "Notes 2," convention dies in 3 weeks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Airtable&lt;/strong&gt; — great, until the free plan cap. Then you're paying $20/user/month for something a Notion page does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Linear project view&lt;/strong&gt; — Linear wants everything to be an issue with an assignee. Builds don't have assignees. Force-fitting them broke Linear's own workflows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slack &lt;code&gt;#builds&lt;/code&gt; channel&lt;/strong&gt; — worst for retrieval. Nobody scrolls back 6 weeks to find a build. Slack search is okay but rewards recency, not structure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notion database wins because it's schemaless-enough to start ugly and iterate, structured-enough to filter, and searchable enough that Cmd-F works.&lt;/p&gt;

&lt;p&gt;One bonus: give the database a public read-only share link. Stakeholders stop DM'ing you "which build is prod on?" and start bookmarking the URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  The meta-lesson
&lt;/h2&gt;

&lt;p&gt;Build artifact organization is a &lt;strong&gt;design problem&lt;/strong&gt;, not an engineering one. The engineering (naming, metadata, CI) is 20% of the value. The other 80% is picking one convention and enforcing it socially — nobody renames files, nobody ships without the tracker row, nobody sends builds via DM.&lt;/p&gt;

&lt;p&gt;If you're setting this up for the first time, budget half a day for the convention and half a day for onboarding the team to it. If your team already has 400 unstructured artifacts, don't retroactively rename them — start clean from today's build. Sunk cost is real; back-labeling artifacts is not worth the hours.&lt;/p&gt;




&lt;p&gt;What does your build artifact naming look like right now? Drop your convention in the comments — I'm still collecting variants, especially from teams running more than one app out of a monorepo.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>expo</category>
      <category>devops</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Cutting React Native template support 70% with docs</title>
      <dc:creator>Chris F A</dc:creator>
      <pubDate>Fri, 24 Jul 2026 12:42:15 +0000</pubDate>
      <link>https://dev.to/chris_fa_8fca9f4ba09d963/cutting-react-native-template-support-70-with-docs-4eia</link>
      <guid>https://dev.to/chris_fa_8fca9f4ba09d963/cutting-react-native-template-support-70-with-docs-4eia</guid>
      <description>&lt;p&gt;We sell React Native templates. In Q1 2026 we rewrote our docs. Support tickets dropped 70%. This is the exact process, the exact structure, and the numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The audit (do this first)
&lt;/h2&gt;

&lt;p&gt;Before rewriting a single page, tag every support ticket for six weeks. Six columns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csvs"&gt;&lt;code&gt;&lt;span class="k"&gt;date&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;customer&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;category&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;root&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;cause&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;resolution&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;docs&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Categories we ended up with: &lt;code&gt;install&lt;/code&gt;, &lt;code&gt;configure&lt;/code&gt;, &lt;code&gt;extend&lt;/code&gt;, &lt;code&gt;ship&lt;/code&gt;, &lt;code&gt;billing&lt;/code&gt;, &lt;code&gt;other&lt;/code&gt;.&lt;br&gt;
Root causes: &lt;code&gt;docs_missing&lt;/code&gt;, &lt;code&gt;docs_wrong&lt;/code&gt;, &lt;code&gt;docs_unfindable&lt;/code&gt;, &lt;code&gt;product_bug&lt;/code&gt;, &lt;code&gt;user_misunderstanding&lt;/code&gt;, &lt;code&gt;out_of_scope&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The audit output for us:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;% of tickets&lt;/th&gt;
&lt;th&gt;Docs-fixable?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;install&lt;/td&gt;
&lt;td&gt;24%&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;configure&lt;/td&gt;
&lt;td&gt;21%&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;extend&lt;/td&gt;
&lt;td&gt;18%&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ship&lt;/td&gt;
&lt;td&gt;5%&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;billing&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;other&lt;/td&gt;
&lt;td&gt;22%&lt;/td&gt;
&lt;td&gt;mostly no&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;68% of tickets were docs-fixable. That number is the whole rest of the article.&lt;/p&gt;
&lt;h2&gt;
  
  
  The four-mode structure
&lt;/h2&gt;

&lt;p&gt;Restructure your doc site around the four stages a new customer hits, 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;docs/
├── getting-started/     # install
│   ├── installation.mdx
│   ├── environment.mdx
│   ├── quick-start.mdx
│   └── folder-structure.mdx
├── core-concepts/       # configure
│   ├── supabase.mdx
│   ├── expo-integration.mdx
│   └── ui-components.mdx
├── extend/              # extend
│   ├── adding-a-screen.mdx
│   ├── auth-guards.mdx
│   └── swapping-ai-provider.mdx
└── ship/                # ship
    ├── eas-build.mdx
    ├── app-store.mdx
    └── ota-updates.mdx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Order matters. Doc-site ordering is not aesthetic. It is a state machine — each page assumes the customer completed the previous one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five rules
&lt;/h2&gt;

&lt;p&gt;Print these. Stick them to a monitor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. First paragraph states the outcome, not the topic.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Bad:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Authentication&lt;/span&gt;

This page covers authentication in the template.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Adding auth to a new screen&lt;/span&gt;

By the end of this page, tapping a locked screen will redirect
signed-out users to &lt;span class="sb"&gt;`/sign-in`&lt;/span&gt; and preserve the deep link they
originally opened.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Every code block is copy-paste runnable.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Bad:&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;// ... existing imports&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;Screen&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSupabase&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good:&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;useSupabase&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;Redirect&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;expo-router&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;ProtectedScreen&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSupabase&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;loading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&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;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Redirect&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/sign-in"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;YourScreenContents&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Every page links to the next page.&lt;/strong&gt; Never let the customer bounce to Google.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. If you answer the same email twice, the docs get updated that day.&lt;/strong&gt; Same day. This is the only rule with real leverage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Ship a &lt;code&gt;claude.md&lt;/code&gt; at the project root.&lt;/strong&gt; If your customer opens the template in Claude Code, Cursor, or any AI-native editor, the AI reads this file first. The one we ship in every &lt;a href="https://www.applighter.com/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=how-we-cut-template-support-load-70-with-better-docs" rel="noopener noreferrer"&gt;Applighter&lt;/a&gt; template looks roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Applighter — &amp;lt;template name&amp;gt;&lt;/span&gt;

&lt;span class="gu"&gt;## Stack&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Expo 54, React Native 0.76
&lt;span class="p"&gt;-&lt;/span&gt; Supabase (auth, Postgres, storage, edge functions)
&lt;span class="p"&gt;-&lt;/span&gt; NativeWind 4, React Native Reusables (primitives)
&lt;span class="p"&gt;-&lt;/span&gt; TanStack Query 5

&lt;span class="gu"&gt;## Key files&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`app/(auth)/*`&lt;/span&gt; — auth flows
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`app/(app)/*`&lt;/span&gt; — signed-in surface
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`lib/supabase.ts`&lt;/span&gt; — client + typed helpers
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`supabase/migrations/*`&lt;/span&gt; — schema (RLS policies live here)

&lt;span class="gu"&gt;## Conventions&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; All server access via TanStack Query hooks in &lt;span class="sb"&gt;`hooks/`&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Never call supabase-js directly from a component
&lt;span class="p"&gt;-&lt;/span&gt; Add new tables with a migration, not the dashboard

&lt;span class="gu"&gt;## Env vars&lt;/span&gt;
See &lt;span class="sb"&gt;`.env.example`&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI now writes correct code the first time. The customer never emails you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Operational loop
&lt;/h2&gt;

&lt;p&gt;Once the docs exist, keep them alive:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Log every ticket in a shared sheet (6 columns above).&lt;/li&gt;
&lt;li&gt;30-minute triage every Friday. Flag anything asked twice.&lt;/li&gt;
&lt;li&gt;Doc gap → paragraph inline or new page scaffolded same day.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. No LLM classification. No analytics vendor. The marginal cost of writing one paragraph is much lower than the marginal cost of answering the same email eight times.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Support tickets: &lt;strong&gt;−70%&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Docs-caused refunds: &lt;strong&gt;−85%&lt;/strong&gt; (from ~4% to ~0.6% of purchases)&lt;/li&gt;
&lt;li&gt;Time-to-first-customized-screen: &lt;strong&gt;2–4 hours → 25–45 min&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Five-star reviews mentioning "docs": &lt;strong&gt;2 → 34&lt;/strong&gt; across two quarters&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What we didn't do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;No chatbot. Deflection metrics are gameable.&lt;/li&gt;
&lt;li&gt;No paywalled docs. Public and indexable.&lt;/li&gt;
&lt;li&gt;No community forum. Half-populated forums are worse than none.&lt;/li&gt;
&lt;li&gt;No codebase restructure to match docs. Different audiences, different shapes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reference docs I used
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://diataxis.fr/" rel="noopener noreferrer"&gt;Diátaxis framework&lt;/a&gt; — the closest published version of what we independently arrived at&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.expo.dev/" rel="noopener noreferrer"&gt;Expo docs&lt;/a&gt; — the bar for RN documentation&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://supabase.com/docs" rel="noopener noreferrer"&gt;Supabase docs&lt;/a&gt; — same&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you sell anything to developers, run the audit this week — tag your last hundred tickets and the shape of your docs problem will be obvious within an hour. Drop a comment with what your top ticket category turns out to be; curious whether install beats configure for everyone or just for us.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>expo</category>
      <category>supabase</category>
      <category>documentation</category>
    </item>
    <item>
      <title>The Boring React Native Stack for New Founders</title>
      <dc:creator>Chris F A</dc:creator>
      <pubDate>Wed, 22 Jul 2026 09:51:29 +0000</pubDate>
      <link>https://dev.to/chris_fa_8fca9f4ba09d963/the-boring-react-native-stack-for-new-founders-4dnp</link>
      <guid>https://dev.to/chris_fa_8fca9f4ba09d963/the-boring-react-native-stack-for-new-founders-4dnp</guid>
      <description>&lt;p&gt;Every week, first-time React Native founders ask us the same question: &lt;em&gt;what stack should I use?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here's our copy-pasted answer, with the receipts.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client:   Expo SDK 54 (managed) + Expo Router + TypeScript
Styling:  NativeWind v4
Data:     TanStack Query v5 + React Hook Form
Motion:   Reanimated 4.1
Backend:  Supabase (Postgres + Auth + Storage + Edge Functions + RLS)
Payments: Stripe (payment mode, one-time)
Builds:   EAS Build + EAS Update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;No Redux. No Firebase. No custom auth. No Fastlane. No bare workflow.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why "boring"
&lt;/h2&gt;

&lt;p&gt;You have zero product-market fit information on day zero. Every stack decision you make on day zero is a bet without data. The correct bet is the one that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Has the most Stack Overflow answers&lt;/li&gt;
&lt;li&gt;Has the shortest "hello world → first user" path&lt;/li&gt;
&lt;li&gt;Has the lowest switching cost when you learn you were wrong&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's a boring stack, definitionally.&lt;/p&gt;
&lt;h2&gt;
  
  
  The client
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Expo SDK 54 (managed workflow)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-expo-app@latest my-app
&lt;span class="nb"&gt;cd &lt;/span&gt;my-app
npx expo start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Don't &lt;code&gt;expo prebuild&lt;/code&gt;. Don't eject. The managed workflow gives you EAS Build (cloud iOS builds without a Mac), &lt;code&gt;expo-updates&lt;/code&gt; for OTA JS pushes, and pre-wrapped native modules for 90% of what you'll need.&lt;/p&gt;
&lt;h3&gt;
  
  
  Expo Router
&lt;/h3&gt;

&lt;p&gt;File-based routing. If you know Next.js App Router, you know Expo Router.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/
├── (tabs)/
│   ├── _layout.tsx
│   ├── index.tsx
│   └── profile.tsx
├── notes/
│   └── [id].tsx
└── _layout.tsx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Deep links, universal links, and typed routes come for free. Docs: &lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://docs.expo.dev/router/introduction/" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;docs.expo.dev&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;h3&gt;
  
  
  NativeWind v4
&lt;/h3&gt;

&lt;p&gt;Tailwind for React Native.&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex-1 bg-white dark:bg-neutral-900 px-4"&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;Text&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-2xl font-semibold text-neutral-900 dark:text-white"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    Hello
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Text&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your team already knows Tailwind. Move on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data layer
&lt;/h2&gt;

&lt;h3&gt;
  
  
  TanStack Query v5 for anything that touches the network
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&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;isPending&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;queryKey&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;notes&lt;/span&gt;&lt;span class="dl"&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="na"&gt;queryFn&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="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;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;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&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="s1"&gt;user_id&lt;/span&gt;&lt;span class="dl"&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="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cache invalidation, background refetch, optimistic updates, offline persistence — all included. You do not need Redux.&lt;/p&gt;

&lt;h3&gt;
  
  
  React Hook Form for forms
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;control&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleSubmit&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useForm&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Note&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;Controller&lt;/span&gt;
  &lt;span class="nx"&gt;control&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;control&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{({&lt;/span&gt; &lt;span class="nx"&gt;field&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TextInput&lt;/span&gt; &lt;span class="na"&gt;onChangeText&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&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;Boring. Fast. Good TypeScript types. The end.&lt;/p&gt;

&lt;h2&gt;
  
  
  Motion: Reanimated 4.1
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&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="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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gesture&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Gesture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Pan&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;onUpdate&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&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;offset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;translationX&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Worklets run on the UI thread. 60fps by default. No bridging.&lt;/p&gt;

&lt;h2&gt;
  
  
  The backend: Supabase, all of it
&lt;/h2&gt;

&lt;p&gt;The reason Supabase is boring in the good way: it's Postgres. When your query is slow at 11pm, you open the SQL editor and read the plan.&lt;/p&gt;

&lt;h3&gt;
  
  
  Row-Level Security (non-negotiable)
&lt;/h3&gt;

&lt;p&gt;Enable RLS on every table before the table has data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;alter&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;notes&lt;/span&gt; &lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt; &lt;span class="k"&gt;level&lt;/span&gt; &lt;span class="k"&gt;security&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;"notes_owner_select"&lt;/span&gt;
  &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;notes&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;"notes_owner_insert"&lt;/span&gt;
  &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;notes&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now no client bug, no leaked JWT, no misconfigured API route can leak user A's rows to user B. The database refuses. This is the single highest-leverage security decision you make.&lt;/p&gt;

&lt;h3&gt;
  
  
  Edge Functions for secrets
&lt;/h3&gt;

&lt;p&gt;Every third-party API key (OpenAI, Anthropic, Stripe, Deepgram) lives in an Edge Function. Zero secrets in the client bundle:&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;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &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="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;apiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&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;OPENAI_API_KEY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
  &lt;span class="c1"&gt;// ... call OpenAI, stream response back via SSE&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Payments: Stripe (one-time, not subscriptions)
&lt;/h2&gt;

&lt;p&gt;First-time founders default to subscriptions because that's what YC tells them to want. Ship one-time first — simpler dunning, faster checkout, no churn number to obsess over on Monday mornings.&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;session&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;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sessions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;payment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;line_items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;priceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="na"&gt;success_url&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;origin&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/success?session_id={CHECKOUT_SESSION_ID}`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;cancel_url&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;origin&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/cancel`&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;Webhook → &lt;code&gt;product_licenses&lt;/code&gt; table → grant unlocked. &lt;a href="https://www.applighter.com/apps?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=boring-react-native-tech-stack-for-new-founders" rel="noopener noreferrer"&gt;Applighter&lt;/a&gt; itself ships in this exact pattern — every template in the catalog uses this one-time-purchase flow end-to-end.&lt;/p&gt;

&lt;h2&gt;
  
  
  Builds: EAS
&lt;/h2&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; ios &lt;span class="nt"&gt;--profile&lt;/span&gt; production
eas submit &lt;span class="nt"&gt;--platform&lt;/span&gt; ios &lt;span class="nt"&gt;--latest&lt;/span&gt;
eas update &lt;span class="nt"&gt;--branch&lt;/span&gt; production &lt;span class="nt"&gt;--message&lt;/span&gt; &lt;span class="s2"&gt;"fix: crash on launch"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cloud iOS builds. No Mac required. OTA updates ship in minutes instead of days.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Piece&lt;/th&gt;
&lt;th&gt;Boring pick&lt;/th&gt;
&lt;th&gt;Trendy alt&lt;/th&gt;
&lt;th&gt;Why v1 skips trendy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Framework&lt;/td&gt;
&lt;td&gt;Expo managed&lt;/td&gt;
&lt;td&gt;Bare RN&lt;/td&gt;
&lt;td&gt;2 weeks re-wrapping native libs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Styling&lt;/td&gt;
&lt;td&gt;NativeWind&lt;/td&gt;
&lt;td&gt;Tamagui&lt;/td&gt;
&lt;td&gt;Your team knows Tailwind&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td&gt;TanStack Query + useState&lt;/td&gt;
&lt;td&gt;Redux / Zustand&lt;/td&gt;
&lt;td&gt;95% of state is server state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;Supabase&lt;/td&gt;
&lt;td&gt;Firebase&lt;/td&gt;
&lt;td&gt;Firestore's query model is a trap&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td&gt;Supabase Auth&lt;/td&gt;
&lt;td&gt;Clerk&lt;/td&gt;
&lt;td&gt;It's already bundled with the DB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payments&lt;/td&gt;
&lt;td&gt;Stripe one-time&lt;/td&gt;
&lt;td&gt;RevenueCat subs&lt;/td&gt;
&lt;td&gt;Subs are v2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Builds&lt;/td&gt;
&lt;td&gt;EAS&lt;/td&gt;
&lt;td&gt;Fastlane&lt;/td&gt;
&lt;td&gt;Cloud-native, no Mac needed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What we deliberately omit
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Redux / Zustand&lt;/strong&gt; — see above&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GraphQL&lt;/strong&gt; — Postgres + PostgREST is enough&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sentry&lt;/strong&gt; — recommended, but not day one&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monorepo&lt;/strong&gt; — great for 3+ apps, overkill for one&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detox / Maestro&lt;/strong&gt; — unit tests first, E2E after 6 months&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The AI stack question
&lt;/h2&gt;

&lt;p&gt;"But my app is an AI app — does the stack change?"&lt;/p&gt;

&lt;p&gt;Almost not at all:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+ Supabase Edge Function (holds OPENAI_API_KEY)
+ Postgres table for chat history (RLS on)
+ SSE streaming back to client (rendered with Reanimated)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. AI features are just features.&lt;/p&gt;

&lt;h2&gt;
  
  
  The playbook
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;npx create-expo-app&lt;/code&gt; on latest stable SDK&lt;/li&gt;
&lt;li&gt;Add NativeWind + Expo Router + Reanimated + TanStack Query + RHF&lt;/li&gt;
&lt;li&gt;Supabase project → schema in migrations → RLS before any rows&lt;/li&gt;
&lt;li&gt;Every third-party key in Edge Functions&lt;/li&gt;
&lt;li&gt;Stripe &lt;code&gt;payment&lt;/code&gt; mode → webhook → &lt;code&gt;product_licenses&lt;/code&gt; table&lt;/li&gt;
&lt;li&gt;EAS → TestFlight in the first two weeks&lt;/li&gt;
&lt;li&gt;Ship features. Reconsider stack in 18 months.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Or: skip steps 1–5
&lt;/h2&gt;

&lt;p&gt;Our templates are this exact stack, wired end-to-end, with real Stripe flow and RLS on every table — the catalog is linked up in the payments section. If you'd rather start on hour one with a working app instead of hour thirty, that's what they're for.&lt;/p&gt;

&lt;p&gt;Either way — pick a boring stack and ship. Reading yet another stack post is the enemy.&lt;/p&gt;

&lt;p&gt;What's the stack you actually shipped with — and what would you cut from this list? Drop a comment.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>expo</category>
      <category>supabase</category>
      <category>mobile</category>
    </item>
    <item>
      <title>The Design Handoff Changed — Here's What AI-Legible Specs Look Like</title>
      <dc:creator>Chris F A</dc:creator>
      <pubDate>Wed, 22 Jul 2026 09:44:37 +0000</pubDate>
      <link>https://dev.to/chris_fa_8fca9f4ba09d963/the-design-handoff-changed-heres-what-ai-legible-specs-look-like-38ki</link>
      <guid>https://dev.to/chris_fa_8fca9f4ba09d963/the-design-handoff-changed-heres-what-ai-legible-specs-look-like-38ki</guid>
      <description>&lt;p&gt;A year ago, the design handoff was: Figma file, Slack thread, engineer opens Figma, translates layout, spends 4 hours on one screen. Feedback loop measured in days.&lt;/p&gt;

&lt;p&gt;Today, if the engineer is using Claude Code and your Figma has the right structure, one screen can go from design-approved to feature-branch in 20 minutes. But only if design changed how it hands off. Otherwise you get the AI-slop version.&lt;/p&gt;

&lt;p&gt;Here's what changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The new handoff
&lt;/h2&gt;

&lt;p&gt;The AI does the layout translation. What it needs from you:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A structured spec&lt;/strong&gt; (not just a Figma link).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Named components and design tokens&lt;/strong&gt; (not raw pixel values).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State variants explicit&lt;/strong&gt; (loading / empty / error / success, all designed).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A working example somewhere in the codebase&lt;/strong&gt; the AI can pattern-match against.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Give the AI these and the engineer's role shifts from 'implementer' to 'QA'.&lt;/p&gt;

&lt;h2&gt;
  
  
  What makes a spec AI-legible
&lt;/h2&gt;

&lt;p&gt;Bad spec (what most teams still write):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;'Login screen with email + password. Match the style of the signup screen.'&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Good spec:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gs"&gt;**Login screen**&lt;/span&gt; (&lt;span class="sb"&gt;`app/(auth)/login.tsx`&lt;/span&gt;)

&lt;span class="gs"&gt;**Layout:**&lt;/span&gt; Centered card, 24px padding, max-width 400px, radius 12px.

&lt;span class="gs"&gt;**Contents (top-down):**&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Logo (asset: &lt;span class="sb"&gt;`assets/logo.svg`&lt;/span&gt;, 64x64px)
&lt;span class="p"&gt;-&lt;/span&gt; Heading: 'Welcome back' (typography token: &lt;span class="sb"&gt;`heading/md`&lt;/span&gt;)
&lt;span class="p"&gt;-&lt;/span&gt; Email input (component: &lt;span class="sb"&gt;`&amp;lt;Input type="email" label="Email" /&amp;gt;`&lt;/span&gt;)
&lt;span class="p"&gt;-&lt;/span&gt; Password input (component: &lt;span class="sb"&gt;`&amp;lt;Input type="password" label="Password" /&amp;gt;`&lt;/span&gt;)
&lt;span class="p"&gt;-&lt;/span&gt; Primary button: 'Sign in' (component: &lt;span class="sb"&gt;`&amp;lt;Button variant="primary" fullWidth /&amp;gt;`&lt;/span&gt;)
&lt;span class="p"&gt;-&lt;/span&gt; Text link: 'Forgot password?' → &lt;span class="sb"&gt;`/(auth)/reset`&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Divider with 'or'
&lt;span class="p"&gt;-&lt;/span&gt; Google OAuth button (component: &lt;span class="sb"&gt;`&amp;lt;Button variant="oauth" provider="google" /&amp;gt;`&lt;/span&gt;)

&lt;span class="gs"&gt;**States:**&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Idle: as above.
&lt;span class="p"&gt;-&lt;/span&gt; Loading: primary button disabled + spinner, other fields disabled.
&lt;span class="p"&gt;-&lt;/span&gt; Error (invalid credentials): red banner above form, 'Invalid email or password.'
&lt;span class="p"&gt;-&lt;/span&gt; Success: redirect to &lt;span class="sb"&gt;`/(protected)/home`&lt;/span&gt;.

&lt;span class="gs"&gt;**Existing pattern to match:**&lt;/span&gt; &lt;span class="sb"&gt;`app/(auth)/signup.tsx`&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second spec, an AI can implement in one shot. The first spec, the AI guesses and you get random.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where design still has to be in the room
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deciding what should exist on the screen.&lt;/strong&gt; The AI can render anything; deciding what shouldn't be there is the design call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reviewing motion / interaction.&lt;/strong&gt; Static screenshots don't communicate feel. Design still needs to test the interaction on-device.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sign-off on brand-adjacent decisions.&lt;/strong&gt; Illustrations, iconography, tone of empty-state copy. AI generates competent, not distinctive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The system-level view.&lt;/strong&gt; Any single screen an AI can build. The design system it's building against is your job.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The design-to-Claude-Code checklist
&lt;/h2&gt;

&lt;p&gt;Before you hand a spec to be implemented via AI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Every component named matches a real component in the codebase (or is explicitly new with an inline spec).&lt;/li&gt;
&lt;li&gt;[ ] Every typography/color/spacing value is a design token, not a hex/px.&lt;/li&gt;
&lt;li&gt;[ ] All five states (loading / empty / error / success / boundary) are designed.&lt;/li&gt;
&lt;li&gt;[ ] Copy is finalised, not lorem ipsum.&lt;/li&gt;
&lt;li&gt;[ ] Analytics events (what to &lt;code&gt;track()&lt;/code&gt; on interactions) are listed.&lt;/li&gt;
&lt;li&gt;[ ] The existing screen most similar to this one is linked.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That checklist is 10 minutes of design time. It saves the engineer an hour and gives you a much better first cut back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical: what to put in your team's design workflow
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Design deliverable is now a Markdown spec + Figma link, not just a Figma link.&lt;/li&gt;
&lt;li&gt;The Markdown lives in the repo alongside the code (in &lt;code&gt;/docs/screens/&lt;/code&gt; or similar).&lt;/li&gt;
&lt;li&gt;The AI reads both when implementing.&lt;/li&gt;
&lt;li&gt;Design reviews the implemented screen, not the code — the AI wrote what you told it to; you check that you told it right.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to see this spec-first workflow in action end-to-end, &lt;a href="https://www.applighter.com/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=design-handoff-claude-code" rel="noopener noreferrer"&gt;Applighter&lt;/a&gt; is built around exactly this loop — structured specs in, working screens out.&lt;/p&gt;

&lt;p&gt;Design's role isn't shrinking with AI. It's shifting to more explicit specification and more rigorous review — the parts that were always undervalued when handoffs were 'just look at the Figma'. The tools finally reward doing them well.&lt;/p&gt;

&lt;p&gt;What does your team's handoff look like right now — still Figma-link-and-pray, or have you started writing specs the AI can read? Drop a comment.&lt;/p&gt;

</description>
      <category>design</category>
      <category>ai</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Sell Templates vs SaaS — 12 Months of Real Numbers</title>
      <dc:creator>Chris F A</dc:creator>
      <pubDate>Mon, 20 Jul 2026 09:59:07 +0000</pubDate>
      <link>https://dev.to/chris_fa_8fca9f4ba09d963/sell-templates-vs-saas-12-months-of-real-numbers-4ndc</link>
      <guid>https://dev.to/chris_fa_8fca9f4ba09d963/sell-templates-vs-saas-12-months-of-real-numbers-4ndc</guid>
      <description>&lt;h1&gt;
  
  
  Sell Templates vs SaaS — 12 Months of Real Numbers
&lt;/h1&gt;

&lt;p&gt;If you're an indie hacker on the "SaaS by default" hype train, this is the counter-post.&lt;/p&gt;

&lt;p&gt;We ran a React Native template shop (&lt;a href="https://www.applighter.com/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=sell-a-saas-or-sell-templates-our-12-month-numbers" rel="noopener noreferrer"&gt;Applighter&lt;/a&gt;) for twelve months instead of building the SaaS everyone told us to build. Below are the actual numbers, the trade-offs, and the decision framework we now use.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Templates:&lt;/strong&gt; higher AOV per sale, ~zero infra, zero pager, bounded support, no compounding.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SaaS:&lt;/strong&gt; compounding MRR, real churn, real pager, real infra bill, real feedback loop.&lt;/li&gt;
&lt;li&gt;Neither is passive. Both are full-time jobs. Pick based on what kind of job you want.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The 12-month table
&lt;/h2&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;Templates (us)&lt;/th&gt;
&lt;th&gt;Solo SaaS (previous project)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Avg deal size&lt;/td&gt;
&lt;td&gt;$148&lt;/td&gt;
&lt;td&gt;$19/mo (~$228 annualized)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Refund / early cancel&lt;/td&gt;
&lt;td&gt;3.4%&lt;/td&gt;
&lt;td&gt;~5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Support tickets / sale&lt;/td&gt;
&lt;td&gt;0.6&lt;/td&gt;
&lt;td&gt;3.1 / active seat / yr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monthly infra&lt;/td&gt;
&lt;td&gt;~$40&lt;/td&gt;
&lt;td&gt;~$620&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time to first $&lt;/td&gt;
&lt;td&gt;Day 11&lt;/td&gt;
&lt;td&gt;Day 47&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Weekend incidents (12mo)&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Churn&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;7.1% / mo&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The template stack
&lt;/h2&gt;

&lt;p&gt;Every Applighter template ships with the same base:&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;# Every template is:&lt;/span&gt;
&lt;span class="c"&gt;# - Expo SDK 54&lt;/span&gt;
&lt;span class="c"&gt;# - React Native + TypeScript&lt;/span&gt;
&lt;span class="c"&gt;# - NativeWind styling&lt;/span&gt;
&lt;span class="c"&gt;# - Supabase (postgres + auth + storage + edge fns)&lt;/span&gt;
&lt;span class="c"&gt;# - Stripe for in-app monetization&lt;/span&gt;
&lt;span class="c"&gt;# - EAS Build ready&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tech consolidation matters commercially. When every product uses the same base, upgrades are a single sweep, not a per-product project.&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/services/AIService.ts — swappable AI backend&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;AIService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;transcribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audio&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&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;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Message&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;Message&lt;/span&gt;&lt;span class="o"&gt;&amp;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;// Concrete: OpenAIService, AnthropicService, etc.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That interface is the reason we can promise "swap GPT-4o for Claude with a config change" and mean it. It's also the reason support tickets stay bounded — we don't get "your AI provider is broken" tickets because the provider is &lt;em&gt;the buyer's&lt;/em&gt; choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where SaaS beats templates
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Compounding revenue.&lt;/strong&gt; A template sale today doesn't pay you next month. A SaaS seat sold today at 3% monthly churn pays you ~33 more times. That's the whole reason people build SaaS. It's real and it matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Feedback.&lt;/strong&gt; SaaS users are in your product daily. Template buyers vanish into their own codebase. You learn less than you'd think.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing power.&lt;/strong&gt; Raise SaaS prices annually. Templates are mostly a one-shot pricing decision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enterprise ceiling.&lt;/strong&gt; SaaS scales into six- and seven-figure ACVs. Templates cap somewhere around a few $k per license.&lt;/p&gt;

&lt;p&gt;If your product genuinely requires a hosted component — real-time collab, an API you route traffic through, a data pipeline the buyer can't self-host — templates are not viable. Build the SaaS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where templates beat SaaS
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Infra is a rounding error.&lt;/strong&gt; ~$40/mo covers our site, DB, and email. A SaaS at even modest scale needs multiples of that before month one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Support is bounded by the sale.&lt;/strong&gt; SaaS support scales with active seats forever. Template support scales with new sales and drops off after week two per customer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No pager.&lt;/strong&gt; No servers = no wakeups. Our only two "on-call incidents" in a year were us breaking our own site.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zero data liability.&lt;/strong&gt; We hold no customer PII beyond orders. Breaches don't propagate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing that fits on a napkin.&lt;/strong&gt; Buy once, own forever, get updates. Try writing that on a SaaS pricing page.&lt;/p&gt;

&lt;h2&gt;
  
  
  The revenue mix
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stream&lt;/th&gt;
&lt;th&gt;Share&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Template licenses&lt;/td&gt;
&lt;td&gt;71%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bundle upgrades&lt;/td&gt;
&lt;td&gt;18%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom / enterprise&lt;/td&gt;
&lt;td&gt;8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Affiliate + referrals&lt;/td&gt;
&lt;td&gt;3%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Notice: no SaaS component. We prototyped a hosted "template runner" for a week and killed it — the moment you host something, your infra + support burden explodes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The uncomfortable parts
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Revenue is spiky.&lt;/strong&gt; No MRR floor means a slow week hurts. We use a rolling 4-week average for any decision that matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Marketing is the whole job.&lt;/strong&gt; No retention means every dollar needs a new buyer. If you hate writing, posting, and networking, don't sell templates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Piracy exists.&lt;/strong&gt; Templates hit nulled sites within a month. Buyers who pay ≠ people who pirate. File DMCAs, move on.&lt;/p&gt;

&lt;h2&gt;
  
  
  The math
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Solo dev: ~1920 productive hours/year.&lt;/li&gt;
&lt;li&gt;Each template sale ≈ 30 min of amortized effort.&lt;/li&gt;
&lt;li&gt;SaaS attempt with same input ≈ 45 seats × $19/mo × 12 = ~$10k ARR at month 12.&lt;/li&gt;
&lt;li&gt;Template shop crossed that in month 3.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Templates front-load payoff. SaaS back-loads it. If your runway is short and your compounding assumption is optimistic, front-loading is safer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related reads on this blog
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.applighter.com/blog/best-react-native-boilerplates-in-2026-honest-comparison?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=sell-a-saas-or-sell-templates-our-12-month-numbers" rel="noopener noreferrer"&gt;Best React Native boilerplates in 2026: honest comparison&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.applighter.com/blog/the-react-native-template-industry-is-mostly-broken-heres-how-to-fix-it?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=sell-a-saas-or-sell-templates-our-12-month-numbers" rel="noopener noreferrer"&gt;The React Native template industry is mostly broken&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.applighter.com/blog/how-long-does-it-actually-take-to-ship-a-react-native-app-we-tracked-30-indie-d?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=sell-a-saas-or-sell-templates-our-12-month-numbers" rel="noopener noreferrer"&gt;How long does it actually take to ship a React Native app&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  External refs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.expo.dev/" rel="noopener noreferrer"&gt;Expo docs&lt;/a&gt; — the runtime under every template we sell.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://supabase.com/docs/guides/auth/row-level-security" rel="noopener noreferrer"&gt;Supabase RLS guide&lt;/a&gt; — the reason we can ship a real backend inside every template without hand-holding.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://once.com/" rel="noopener noreferrer"&gt;Basecamp's Once&lt;/a&gt; — the sharpest existing counter-argument to SaaS-forever.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Verdict
&lt;/h2&gt;

&lt;p&gt;Sell templates if:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You can write code other developers will pay for on first look.&lt;/li&gt;
&lt;li&gt;You're willing to be a full-time marketer.&lt;/li&gt;
&lt;li&gt;You want the money now, not later.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If any answer is no, build the SaaS — and price for the pager.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>supabase</category>
      <category>expo</category>
      <category>applighter</category>
    </item>
    <item>
      <title>The 60-Minute Boilerplate Strip-Down Test</title>
      <dc:creator>Chris F A</dc:creator>
      <pubDate>Wed, 08 Jul 2026 08:52:55 +0000</pubDate>
      <link>https://dev.to/chris_fa_8fca9f4ba09d963/the-60-minute-boilerplate-strip-down-test-1cka</link>
      <guid>https://dev.to/chris_fa_8fca9f4ba09d963/the-60-minute-boilerplate-strip-down-test-1cka</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Feature counts on a boilerplate landing page are the easiest thing to market and the least predictive of quality.&lt;/li&gt;
&lt;li&gt;The real test: fork the repo, start a 60-minute timer, and try to &lt;em&gt;cleanly remove&lt;/em&gt; the auth layer.&lt;/li&gt;
&lt;li&gt;If it comes out cleanly, the seams are good and the rest probably follows. If it fights you, you'll be maintaining someone else's architecture forever.&lt;/li&gt;
&lt;li&gt;Three pre-fork red flags: auth sprinkled across screens, DB queries that also do auth checks, and navigation coupled to auth state at the app root.&lt;/li&gt;
&lt;li&gt;Audit for ergonomics ("what happens when I change it?"), not for features.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;When a founder friend asked me how to pick a React Native boilerplate last month, I gave them a test that took me a decade of shipping apps to formulate. It doesn't involve counting features, reading the README, or watching the demo video.&lt;/p&gt;

&lt;p&gt;Here's the test: fork the boilerplate, open a timer, and try to remove one thing in 60 minutes.&lt;/p&gt;

&lt;p&gt;That's it. If you can, buy it. If you can't, walk away.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why feature counts lie about boilerplate quality
&lt;/h2&gt;

&lt;p&gt;Every boilerplate landing page reads the same: "Includes auth, payments, database, push notifications, analytics, dark mode, i18n, [15 more features]." Feature counts are the easiest thing to market and the least predictive of quality.&lt;/p&gt;

&lt;p&gt;What features on a landing page can't tell you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How coupled the auth layer is to the DB layer&lt;/li&gt;
&lt;li&gt;Whether the payment integration assumes Stripe (fine) or hard-codes Stripe API calls throughout the app (not fine)&lt;/li&gt;
&lt;li&gt;How the routing shape will interact with the screens you actually plan to build&lt;/li&gt;
&lt;li&gt;What happens when you want to swap the analytics provider&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of these are ergonomic properties. They're invisible until you try to change something.&lt;/p&gt;

&lt;h2&gt;
  
  
  The strip-down test: remove one thing in 60 minutes
&lt;/h2&gt;

&lt;p&gt;The test I use: fork the repo, then try to remove the auth layer entirely. Not disable, not stub — actually remove it, cleanly, so a new dev looking at the repo wouldn't know auth had ever been there.&lt;/p&gt;

&lt;p&gt;Why auth? Because it's the layer boilerplates most often over-integrate. If it comes out cleanly, everything else probably will too. If it doesn't, you're looking at a boilerplate that will fight you every week the project lives.&lt;/p&gt;

&lt;p&gt;The 60-minute clock is important. It's not "can you do this" — anyone can, given a week. It's "does the codebase make it easy." A good boilerplate should let you remove a major layer in less time than it takes to grab a coffee and read Hacker News.&lt;/p&gt;

&lt;p&gt;Run the same test on payments, on database, on navigation. Whichever one you're most likely to want to swap in your actual project is the layer to prioritize.&lt;/p&gt;

&lt;h2&gt;
  
  
  What passing the test tells you
&lt;/h2&gt;

&lt;p&gt;If you can strip the auth layer in 60 minutes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The boilerplate has clean seams between layers&lt;/li&gt;
&lt;li&gt;Someone thought about extensibility, not just about features&lt;/li&gt;
&lt;li&gt;The codebase respects your future decisions instead of pre-empting them&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you can't:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Layers leak into each other in ways the README doesn't advertise&lt;/li&gt;
&lt;li&gt;Your future "let me swap Clerk for Supabase" migration will be a two-week project&lt;/li&gt;
&lt;li&gt;You'll be maintaining someone else's opinions about architecture forever&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first is a boilerplate. The second is a fork you'll regret.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three signals a boilerplate will fail the test
&lt;/h2&gt;

&lt;p&gt;Before you even fork, three signals that tell you the test will fail:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Auth logic sprinkled across screens instead of contained in a service/hook.&lt;/strong&gt; If &lt;code&gt;AuthContext&lt;/code&gt; is imported in 30+ files, removing auth means editing 30+ files. Contain it and the removal is a delete + import fix.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Database queries wrapped in "business logic" functions that also do auth checks.&lt;/strong&gt; These read as clean at first — one function to fetch a user's data — but they're impossible to reuse if you swap the auth layer. The repository pattern (auth-agnostic data access + a separate auth layer) is what you want.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Navigation coupled to auth state via wrapper components at the root.&lt;/strong&gt; If your &lt;code&gt;App.tsx&lt;/code&gt; has &lt;code&gt;&amp;lt;AuthGate&amp;gt;&amp;lt;Router /&amp;gt;&amp;lt;/AuthGate&amp;gt;&lt;/code&gt; and every screen assumes an authenticated user is present, you'll rewrite the router when you strip auth. Auth-aware routing at the route level, not the app root, is more strippable.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These are all defensible design choices for a specific product. They're bad choices for a starter kit that has to work across many products.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: audit for ergonomics, not for features
&lt;/h2&gt;

&lt;p&gt;The question "what does this boilerplate include" is the wrong first question. The right first question is "what happens when I want to change it?"&lt;/p&gt;

&lt;p&gt;Every boilerplate looks great on day one, when you're using it for exactly the product it was built for. The one you'll want in month three is the one that let you strip the auth layer in an hour when your Clerk trial expired.&lt;/p&gt;

&lt;p&gt;Make that the audit. Feature counts will follow. Ergonomics won't.&lt;/p&gt;




&lt;p&gt;What's the first layer you'd try to rip out of your current boilerplate — auth, payments, or navigation? Drop a comment with the one that would fight you hardest.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>boilerplate</category>
      <category>architecture</category>
      <category>indiehackers</category>
    </item>
  </channel>
</rss>
