<?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: Rémi Buchaillat</title>
    <description>The latest articles on DEV Community by Rémi Buchaillat (@remibuchaillat).</description>
    <link>https://dev.to/remibuchaillat</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3822936%2Fc21fc825-7863-4ee9-bff6-ca88b56e42d0.png</url>
      <title>DEV Community: Rémi Buchaillat</title>
      <link>https://dev.to/remibuchaillat</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/remibuchaillat"/>
    <language>en</language>
    <item>
      <title>From Zero to Shipped: How We Built Indiefy.xyz in a Weekend (With AI as Co-Founder)</title>
      <dc:creator>Rémi Buchaillat</dc:creator>
      <pubDate>Wed, 18 Mar 2026 11:57:08 +0000</pubDate>
      <link>https://dev.to/remibuchaillat/from-zero-to-shipped-how-we-built-indiefyxyz-in-a-weekend-with-ai-as-co-founder-51jo</link>
      <guid>https://dev.to/remibuchaillat/from-zero-to-shipped-how-we-built-indiefyxyz-in-a-weekend-with-ai-as-co-founder-51jo</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;We built a full-stack SaaS product - auth, database, payments, integrations, leaderboard, i18n, mobile UI, SEO - in a single sprint using AI as a build partner. Here's every step, decision, and dead end. By the end of this post, you'll think &lt;em&gt;"wait, I could actually build that."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Idea That Wouldn't Shut Up
&lt;/h2&gt;

&lt;p&gt;Every indie hacker has the same problem: you want to build in public, but the tools suck.&lt;/p&gt;

&lt;p&gt;You've got your MRR on a Google Sheet. Your equity is in a Notion doc nobody reads. Your expenses live in three different apps. And when someone asks &lt;em&gt;"how's the startup going?"&lt;/em&gt; you have to piece together a story from six different tabs.&lt;/p&gt;

&lt;p&gt;That's when the idea hit: &lt;strong&gt;what if Spotify, but for founders?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not Spotify the product - Spotify the &lt;em&gt;vibe&lt;/em&gt;. The dark design. The stats everywhere. The "Wrapped" that makes you feel like your year actually meant something. A profile page at &lt;code&gt;indiefy.xyz/yourname&lt;/code&gt; that tells your whole founder story in one scroll - real MRR, real profit, real equity, real goals.&lt;/p&gt;

&lt;p&gt;We called it &lt;strong&gt;Indiefy&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 0: Write the Spec Before Writing a Single Line of Code
&lt;/h2&gt;

&lt;p&gt;The first thing we did wasn't open a terminal. We wrote an &lt;code&gt;indiefy.md&lt;/code&gt; file - a proper product spec. Not a Notion doc, not a tweet thread. A real, structured document with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The concept in one sentence: &lt;em&gt;"Spotify, but for indie hackers"&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;The target audience (5 different profiles: solo founders, early employees with stock options, angels, advisors, minority partners)&lt;/li&gt;
&lt;li&gt;Every single feature, grouped into sections&lt;/li&gt;
&lt;li&gt;The monetization model (Free vs Pro)&lt;/li&gt;
&lt;li&gt;The tech stack&lt;/li&gt;
&lt;li&gt;A 5-day build timeline&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;This step is the one most builders skip. Don't skip it.&lt;/strong&gt; When you have a spec, you have prompts. When you have prompts, AI can actually build your product instead of guessing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here's an excerpt from ours:&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="gu"&gt;## Features&lt;/span&gt;

&lt;span class="gu"&gt;### Charges &amp;amp; P&amp;amp;L public&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Real Profit = MRR - project costs - pro-rata fixed costs
&lt;span class="p"&gt;-&lt;/span&gt; Runway = savings / total monthly costs
&lt;span class="p"&gt;-&lt;/span&gt; Break-even: at what MRR you cover everything
&lt;span class="gt"&gt;
&amp;gt; First tool that shows REAL project profitability, not just gross MRR.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last line became our &lt;strong&gt;north star&lt;/strong&gt; for every design decision.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: The Design System First (Seriously, Do This First)
&lt;/h2&gt;

&lt;p&gt;We started with design tokens, not code. The whole aesthetic was clear from day one: Spotify dark theme. Exactly this palette:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Token&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Background&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#0a0a0a&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cards&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;#111111&lt;/code&gt; and &lt;code&gt;#1a1a1a&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accent green&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#1DB954&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Primary text&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#FFFFFF&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secondary&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#A3A3A3&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Borders&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#2a2a2a&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two fonts. That's it. &lt;strong&gt;DM Sans&lt;/strong&gt; for body copy. &lt;strong&gt;Syne&lt;/strong&gt; for numbers and titles - that Syne font on a big MRR number hits different.&lt;/p&gt;

&lt;p&gt;The prompt we gave the AI was surgical:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create the complete design system for Indiefy.
Stack: Next.js 16 App Router, Tailwind CSS v4, TypeScript strict.
Design: Spotify-inspired, dark mode only.
Colors: [exact hex values]
Typography: DM Sans (body) + Syne (stats/titles)
Generate: tailwind.config.ts, globals.css, Layout.tsx with left sidebar,
Card.tsx (variants: default/highlight/glass), Typography.tsx
Rules: NO generic Bootstrap-like styles. Server Components by default.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI spit out a complete design system in one shot. Because the prompt was specific. &lt;strong&gt;Garbage in, garbage out - and vice versa.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: The Database Schema - Think Hard Before You Click Enter
&lt;/h2&gt;

&lt;p&gt;The spec: &lt;strong&gt;8 models.&lt;/strong&gt; &lt;code&gt;User&lt;/code&gt;, &lt;code&gt;Project&lt;/code&gt;, &lt;code&gt;Expense&lt;/code&gt;, &lt;code&gt;Revenue&lt;/code&gt;, &lt;code&gt;Goal&lt;/code&gt;, &lt;code&gt;Milestone&lt;/code&gt;, &lt;code&gt;Badge&lt;/code&gt;, &lt;code&gt;Follow&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We wrote out the entire Prisma schema by hand in the spec file before generating it. Every relation, every index, every enum. This took 45 minutes and saved us from three migrations later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;model Project {
  id                 String        @id @default(cuid())
  userId             String
  name               String
  slug               String        @unique
  status             ProjectStatus // ACTIVE | ACQUIRED | DEAD | STEALTH
  equityPercent      Float?
  estimatedValuation Float?
  mrr                Float         @default(0)
  isPublic           Boolean       @default(true)
  showFinancials     Boolean       @default(false)
  // ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;showFinancials&lt;/code&gt; boolean was a key product decision: people can share their profile publicly &lt;em&gt;without&lt;/em&gt; showing the money. This unlocks a whole segment of users who want visibility but not full transparency.&lt;/p&gt;

&lt;p&gt;We also added the &lt;code&gt;Integration&lt;/code&gt; model from day one - Stripe, RevenueCat, LemonSqueezy, Paddle - even before building the integration pages. &lt;strong&gt;Schema-first means you don't paint yourself into a corner later.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Auth - The Part Everyone Dreads
&lt;/h2&gt;

&lt;p&gt;Supabase Auth with Google + GitHub OAuth. Two provider buttons. That's it.&lt;/p&gt;

&lt;p&gt;The interesting part wasn't the OAuth flow - it was the sync between Supabase Auth (which handles the JWT and session) and Prisma (which owns the business data). We wrote a route &lt;code&gt;app/api/auth/sync/route.ts&lt;/code&gt; that fires after OAuth callback and ensures a &lt;code&gt;User&lt;/code&gt; row exists in Postgres.&lt;/p&gt;

&lt;p&gt;The middleware was three lines of logic:&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;// middleware.ts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isProtectedRoute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;matcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;nextUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&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;session&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;getSession&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isProtectedRoute&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;session&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;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&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;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;'&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;url&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;Username validation deserves a mention: lowercase, alphanumeric + hyphens, 3-20 chars, unique. We validated this with Zod server-side and debounced API check client-side. Tiny detail, important UX.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: The Dashboard - Making Numbers Feel Alive
&lt;/h2&gt;

&lt;p&gt;This is where the product really started to look like something.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Four KPI cards at the top:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Total MRR&lt;/li&gt;
&lt;li&gt;Net Profit&lt;/li&gt;
&lt;li&gt;Total Users&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paper Wealth&lt;/strong&gt; (equity x valuations)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last one is what makes Indiefy different from every other dashboard tool. Paper wealth - the sum of all your equity stakes multiplied by estimated valuations - is a number no other tool shows you. It might be zero. It might be fictional. But it's yours, and it's there, and it feels good.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;MRRChart&lt;/code&gt; component used Recharts &lt;code&gt;AreaChart&lt;/code&gt; with a gradient fill. One snippet made the whole chart feel premium:&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="nt"&gt;defs&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="nt"&gt;linearGradient&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mrrGradient"&lt;/span&gt; &lt;span class="na"&gt;x1&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; &lt;span class="na"&gt;y1&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; &lt;span class="na"&gt;x2&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; &lt;span class="na"&gt;y2&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"1"&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="nt"&gt;stop&lt;/span&gt; &lt;span class="na"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"5%"&lt;/span&gt;  &lt;span class="na"&gt;stopColor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"#1DB954"&lt;/span&gt; &lt;span class="na"&gt;stopOpacity&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;0.3&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;stop&lt;/span&gt; &lt;span class="na"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"95%"&lt;/span&gt; &lt;span class="na"&gt;stopColor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"#1DB954"&lt;/span&gt; &lt;span class="na"&gt;stopOpacity&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;0&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;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;linearGradient&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="nt"&gt;defs&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 5: Projects - The Core Loop
&lt;/h2&gt;

&lt;p&gt;The project form was the most complex UI we built. A slider for equity percentage (0-100) with a live calculation beneath it: &lt;em&gt;"Your estimated stake: 47,000"&lt;/em&gt;. A tech stack tag input. Status badges that actually looked like badges, not dropdowns.&lt;/p&gt;

&lt;p&gt;The killer feature was the &lt;strong&gt;acquisition simulator&lt;/strong&gt;. You type a valuation. The number updates instantly:&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;yourShare&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;estimatedSale&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;equityPercent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
&lt;span class="c1"&gt;// Displayed in real-time as you type&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Status colors:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;Color&lt;/th&gt;
&lt;th&gt;Vibe&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ACTIVE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;#1DB954&lt;/code&gt; green&lt;/td&gt;
&lt;td&gt;Go time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ACQUIRED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Blue&lt;/td&gt;
&lt;td&gt;Congrats&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DEAD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Red&lt;/td&gt;
&lt;td&gt;RIP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;STEALTH&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Gray&lt;/td&gt;
&lt;td&gt;We see you&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;After creating a project, we auto-generated a milestone: &lt;code&gt;"Launch de projet"&lt;/code&gt;. Small touch. Makes the product feel alive immediately.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: Expenses &amp;amp; Real P&amp;amp;L
&lt;/h2&gt;

&lt;p&gt;This is the feature that makes Indiefy &lt;strong&gt;honest&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Most "MRR dashboards" just show you gross revenue. Indiefy shows you the real number:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Gross Revenue (MRR):           2,400
Project costs (Vercel, etc): -    89
Fixed costs (pro-rata):      -   340
                             --------
Net Profit:                    1,971  
Margin rate:                      82%
Runway:                    14 months
Break-even MRR:                  429
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;getPnLSummary()&lt;/code&gt; server action ran a single Prisma query that joined revenues, project expenses, and fixed costs pro-rated across all active projects. The math isn't complex - it's just &lt;strong&gt;honest math nobody else was doing&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7: Integrations - The Part That Made It Real
&lt;/h2&gt;

&lt;p&gt;Instead of just supporting Stripe, we ended up integrating &lt;strong&gt;five payment providers&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stripe&lt;/strong&gt; - OAuth Connect, read-only access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RevenueCat&lt;/strong&gt; - API key, for mobile apps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LemonSqueezy&lt;/strong&gt; - API key&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paddle&lt;/strong&gt; - API key&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Polar&lt;/strong&gt; - the new open-source one&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dodo Payments&lt;/strong&gt; - emerging market focus&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The git log tells the story:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;e98928d Polar
2f675fd leaderboard
7457233 dodo payments
67366a0 paddle
829add8 coming soon integrations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each integration followed the same 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="c1"&gt;// lib/integrations/syncEngine.ts&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;syncProjectRevenue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;projectId&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SyncResult&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;integration&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;getActiveIntegration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;projectId&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;data&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;fetchFromProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;integration&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;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project&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="na"&gt;where&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="nx"&gt;projectId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;mrr&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;mrr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;arr&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;arr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;totalRevenue&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;totalRevenue&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;createRevenueEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;projectId&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;checkAndGenerateMilestones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;projectId&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;updateLastSynced&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;integration&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;success&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="na"&gt;mrr&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;mrr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lastSyncedAt&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="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;API keys were stored encrypted with AES-256-GCM. &lt;strong&gt;Not optional.&lt;/strong&gt; You're handling people's financial data.&lt;/p&gt;

&lt;p&gt;The Vercel cron job synced everything at 6am daily:&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="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;vercel.json&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;"crons"&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;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/api/cron/sync-revenues"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"schedule"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0 6 * * *"&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;h2&gt;
  
  
  Step 8: Leaderboard - The Viral Loop
&lt;/h2&gt;

&lt;p&gt;The leaderboard was the feature we almost cut. &lt;em&gt;"Do we really need a ranking system?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Yes. We really did.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2f675fd leaderboard
a2e22b5 explore algo
a17f087 trending algo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three commits across leaderboard features. The ranking algorithm wasn't just "sort by MRR" - we built a &lt;strong&gt;momentum score&lt;/strong&gt;: a composite of MRR velocity (month-over-month growth %), user growth, and update frequency.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A 200 MRR project growing 30% monthly ranks higher than a 5k MRR project that hasn't been updated in three months.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Explore page had its own trending algorithm. Same logic: &lt;strong&gt;recent activity + growth velocity &amp;gt; absolute numbers.&lt;/strong&gt; This made the platform feel fair for people who are just getting started.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 9: Mobile - The Feature You Can't Skip Anymore
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;b8ff2e8 mobile ui
b4fc4a6 bottom nav
471fcce bubbles: mobile nav icons, verified-only badges
3a8410b loading pages
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mobile was a late sprint but not an afterthought. We replaced the left sidebar with a bottom navigation bar on mobile. Standard pattern, but the implementation matters: same server components, just conditional layout based on viewport. The Tailwind breakpoints did the heavy lifting.&lt;/p&gt;

&lt;p&gt;The "bubble" nav icons made the mobile UI feel &lt;strong&gt;native&lt;/strong&gt; rather than "responsive web." Five tabs: Dashboard, Explore, Profile, Goals, Settings.&lt;/p&gt;

&lt;p&gt;Loading states were added systematically across all pages - &lt;strong&gt;skeleton screens, not spinners.&lt;/strong&gt; The difference between a product that feels fast and one that feels slow is often just skeleton screens.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 10: i18n - Because Half Your Users Aren't English
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;d2ca272 i18n
ba6d47f i18n
40a060b Goal i18n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We used &lt;code&gt;next-intl&lt;/code&gt; for internationalization. The routing structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/
  [locale]/
    dashboard/
    profile/
    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every user-facing string became a key:&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;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTranslations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// t('kpi.totalMrr') -&amp;gt; "Total MRR" or "MRR Total"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three commits for i18n because the first one was &lt;em&gt;"we need i18n"&lt;/em&gt;, the second was &lt;em&gt;"actually doing i18n"&lt;/em&gt;, and the third was &lt;em&gt;"ok goals weren't translated yet."&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 11: Security, Legal, Pricing
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;b4fc4a6 legals
a78c03e SECU
5023241 pricing page
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Security was a dedicated commit because integrations meant we had real sensitive data: OAuth tokens, API keys, payment provider access. We audited every route for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Auth checks on every server action&lt;/li&gt;
&lt;li&gt;User ownership verification before any data mutation&lt;/li&gt;
&lt;li&gt;Encrypted storage for all third-party credentials&lt;/li&gt;
&lt;li&gt;Rate limiting on public API endpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Legal pages (Privacy Policy, Terms of Service) were generated but &lt;strong&gt;reviewed&lt;/strong&gt;. You need them. Especially when you're storing financial data and connecting to payment processors.&lt;/p&gt;

&lt;p&gt;The pricing page was the last non-feature piece. Simple: &lt;strong&gt;Free&lt;/strong&gt; (1 project, basic stats) vs &lt;strong&gt;Pro&lt;/strong&gt; (~7/month, everything).&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 12: SEO - The Long Game Starts at Launch
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;7928c7b SEO
eb6e47b SEO
2312193 Analytics
6e88960 Remove from sitemap the not public accounts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dynamic OG images for every public profile (using &lt;code&gt;@vercel/og&lt;/code&gt;). Proper &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; tags. Sitemap generation that &lt;strong&gt;excluded private accounts&lt;/strong&gt; - you don't want to index profiles people marked private.&lt;/p&gt;

&lt;p&gt;Vercel Analytics + Speed Insights went in at the same time. &lt;strong&gt;You can't improve what you don't measure.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Final Stack
&lt;/h2&gt;

&lt;p&gt;After &lt;strong&gt;115 commits&lt;/strong&gt;, here's what Indiefy runs on:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&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;Next.js 16 (App Router)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Styling&lt;/td&gt;
&lt;td&gt;Tailwind CSS v4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;Supabase (PostgreSQL)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ORM&lt;/td&gt;
&lt;td&gt;Prisma 7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td&gt;Supabase Auth (Google + GitHub OAuth)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payments&lt;/td&gt;
&lt;td&gt;Stripe (subscriptions)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integrations&lt;/td&gt;
&lt;td&gt;Stripe, RevenueCat, LemonSqueezy, Paddle, Polar, Dodo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Charts&lt;/td&gt;
&lt;td&gt;Recharts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;i18n&lt;/td&gt;
&lt;td&gt;next-intl&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Validation&lt;/td&gt;
&lt;td&gt;Zod v4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Toasts&lt;/td&gt;
&lt;td&gt;Sonner&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment&lt;/td&gt;
&lt;td&gt;Vercel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OG Images&lt;/td&gt;
&lt;td&gt;@vercel/og&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Analytics&lt;/td&gt;
&lt;td&gt;Vercel Analytics + Speed Insights&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  What Made This Possible
&lt;/h2&gt;

&lt;p&gt;The honest answer: &lt;strong&gt;structured prompts written by a human who knew exactly what to build.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every build step was a complete, specific prompt:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;"Generate X with these exact fields"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Use Zod for validation"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"No &lt;code&gt;any&lt;/code&gt; types"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Server Components by default, use client only when necessary"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Match this exact design system"&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The AI didn't make product decisions. &lt;strong&gt;It executed them.&lt;/strong&gt; The product thinking - what to build, why, in what order - that was 100% human.&lt;/p&gt;

&lt;p&gt;Three things made the AI useful here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Specificity&lt;/strong&gt; - vague prompts get vague code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Existing design system&lt;/strong&gt; - the AI respected our tokens because we defined them first&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step-by-step&lt;/strong&gt; - small, focused tasks beat "build me an app"&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The Numbers
&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;Count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Git commits&lt;/td&gt;
&lt;td&gt;115&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Major features&lt;/td&gt;
&lt;td&gt;~15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payment integrations&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Languages at launch&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bootstrap CSS used&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Could You Build This?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Yes. Genuinely, yes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The technical complexity here isn't exceptional. Next.js App Router is mature. Supabase handles auth beautifully. Prisma removes the pain from database work. Stripe's documentation is excellent.&lt;/p&gt;

&lt;p&gt;What's rare isn't the ability to build it - it's the discipline to &lt;strong&gt;spec it first, prompt it right, and ship before perfecting.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The features we shipped aren't perfect. Some UI could be cleaner. The mobile experience could be smoother. The analytics could be deeper. But the product &lt;strong&gt;exists&lt;/strong&gt;, it &lt;strong&gt;works&lt;/strong&gt;, and it's &lt;strong&gt;live&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Perfect is the enemy of shipped.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What's Next for Indiefy
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Indiefy Wrapped&lt;/strong&gt; (December): your year as a founder, Spotify-style, shareable card&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enjoy life&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you're an indie hacker who wants to display your real journey - not just a landing page, but actual MRR, real profit, equity you might never cash out but still own - create your profile at &lt;strong&gt;&lt;a href="https://indiefy.xyz" rel="noopener noreferrer"&gt;indiefy.xyz&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>buildinpublic</category>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>I built a personal budget app with React Native here's what I learned</title>
      <dc:creator>Rémi Buchaillat</dc:creator>
      <pubDate>Fri, 13 Mar 2026 19:07:47 +0000</pubDate>
      <link>https://dev.to/remibuchaillat/i-built-a-personal-budget-app-with-react-native-heres-what-i-learned-4ohe</link>
      <guid>https://dev.to/remibuchaillat/i-built-a-personal-budget-app-with-react-native-heres-what-i-learned-4ohe</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;An honest retrospective on building Trya, a personal finance mobile app. From the first commit to production crashes, including architectural choices that seemed obvious... until they weren't.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The starting point: a problem I lived myself
&lt;/h2&gt;

&lt;p&gt;Like many people, I had tried every budget app on the market. They were either too complex, too rigid, or simply not adapted to the way I think about money.&lt;/p&gt;

&lt;p&gt;The trigger was a simple observation: there's no single right way to manage your budget. Some people love tracking every euro by category (the classic approach). Others prefer setting broad buckets based on the 50/30/20 rule popularized by Elizabeth Warren: 50% for needs, 30% for wants, 20% for savings.&lt;/p&gt;

&lt;p&gt;So I decided to build an app that respects both approaches. Trya was born.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack: React Native, Expo, and decisions I don't regret
&lt;/h2&gt;

&lt;p&gt;The stack choice was clear from day one: &lt;strong&gt;React Native with Expo&lt;/strong&gt;. Not to follow hype, but for a very pragmatic reason: I wanted to ship on iOS and Android without duplicating business logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expo Router&lt;/strong&gt; for file-based navigation: this is the silent revolution of the Expo ecosystem. Every file inside &lt;code&gt;app/&lt;/code&gt; becomes a route. Nested layouts read like a folder tree. No more manually wiring navigation stacks.&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)/
    budget/
      index.tsx          → /budget
      category-details/
        index.tsx        → /budget/category-details
        transaction/
          index.tsx      → /budget/category-details/transaction
    accounts/
    heritage/
    summary/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When I saw the final structure with dozens of nested screens (edit modals, transaction details, category pickers), I realized just how much pain this choice had saved me.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zustand&lt;/strong&gt; for state management: I could have gone with Redux. I could have gone with Context. I chose Zustand because it imposes nothing. One store = one file, clear logic, no boilerplate. And when you have multiple stores talking to each other (accounts, transactions, budget, net worth), Zustand's lightness really shines.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hardest part: modeling two budget methods
&lt;/h2&gt;

&lt;p&gt;This is where the business domain got genuinely interesting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The classic method&lt;/strong&gt; looks simple on the surface: track expenses by category, see where money goes. But in practice, you need to handle fixed charges, custom trackers, discrete envelopes, one-off vs recurring expenses. Each feature seemed small individually, but together they created a non-trivial surface of complexity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Warren method&lt;/strong&gt; (50/30/20) adds another layer: you need to map the user's categories onto three groups, compute ratios in real time against declared income, and display visual feedback that makes sense even when data is partial.&lt;/p&gt;

&lt;p&gt;The real challenge: both methods must coexist in the same app, share the same transaction data, but present radically different views. The separation between the data layer (Zustand stores) and the presentation layer (feature-based components) turned out to be absolutely critical.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bugs that stick with you
&lt;/h2&gt;

&lt;p&gt;Every mobile developer has their collection of memorable bugs. Here are a few:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The date input crash&lt;/strong&gt;. A date picker that worked perfectly in development but crashed in production on certain Android devices. The culprit? Different date string parsing behavior depending on the system locale. Fix: normalize the format on input, never trust native &lt;code&gt;new Date(string)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Future expenses&lt;/strong&gt;. Letting users log a future fixed charge sounds trivial. In reality, you need to recalculate totals differently depending on whether you're before or after the expense date, without breaking historical charts. An edge case that cost me several iterations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The design switch&lt;/strong&gt;. At some point I decided to overhaul the UI. This kind of decision, made late in development, is often a trap. The lesson: when components are properly decoupled from business logic, redesigning becomes surgical. When they're not, it's a disaster.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Expo Router actually changes
&lt;/h2&gt;

&lt;p&gt;I want to insist on this because it's the technical choice that surprised me most, in a good way.&lt;/p&gt;

&lt;p&gt;Modals, for example. In Trya, almost every important user action opens a &lt;code&gt;modal&lt;/code&gt;: add a transaction, edit a category, update an account. With Expo Router, a modal is just a file with a modal presentation in the layout. It has its own URL. It's navigable. It's independently testable.&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;// app/(tabs)/accounts/edit-bank-modal.tsx&lt;/span&gt;
&lt;span class="c1"&gt;// accessible via router.push('/accounts/edit-bank-modal')&lt;/span&gt;
&lt;span class="c1"&gt;// that's it.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It sounds trivial, but it changes how you think about navigation. You stop wiring callbacks between components, you pass parameters through the URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Internationalization from day one
&lt;/h2&gt;

&lt;p&gt;A decision I don't regret: setting up i18n from the very first commit, with separate &lt;code&gt;en.json&lt;/code&gt; and &lt;code&gt;fr.json&lt;/code&gt; files.&lt;/p&gt;

&lt;p&gt;Personal finance apps have a particular relationship with language. Number formatting, date conventions, percentage notation all vary. If you wait until the end to internationalize, you end up doing &lt;code&gt;find &amp;amp; replace&lt;/code&gt; across dozens of files full of hardcoded strings. The debt is real.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's left to build (and why that's exciting)
&lt;/h2&gt;

&lt;p&gt;Trya is in production. Users rely on it every day to track their budget, manage their net worth, and plan their savings.&lt;/p&gt;

&lt;p&gt;But there's still a lot of ground to cover: smarter recommendation algorithms, more robust multi-device sync, richer visualizations for the wealth tracking features.&lt;/p&gt;

&lt;p&gt;What motivates me most is that every new feature reveals something about how people relate to money. It's not just code, it's applied psychology with React components.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;If you're thinking about building a mobile app solo, here's what I'd carry forward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Choose your stack for the long run, not the hype.&lt;/strong&gt; Expo + Zustand + TypeScript ages well.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model the business domain before touching code.&lt;/strong&gt; The most painful bugs come from a blurry understanding of the problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decouple logic from presentation.&lt;/strong&gt; The day you want to redesign the UI or swap out a budget method, you'll thank yourself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Business edge cases are the real hard bugs.&lt;/strong&gt; Not technical crashes, the cases where logic is almost right.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Trya is available on iOS and Android. If you have questions about the architecture, technical choices, or anything else, comments are open.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>startup</category>
      <category>mobile</category>
      <category>coding</category>
      <category>reactnative</category>
    </item>
  </channel>
</rss>
