<?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: nuttzzz_</title>
    <description>The latest articles on DEV Community by nuttzzz_ (@nuttzzz_).</description>
    <link>https://dev.to/nuttzzz_</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%2F4017671%2Fdc532af9-a6d4-41ed-af89-16ea7278c773.png</url>
      <title>DEV Community: nuttzzz_</title>
      <link>https://dev.to/nuttzzz_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nuttzzz_"/>
    <language>en</language>
    <item>
      <title>I built a fitness SaaS in Next.js — here are the 3 problems that ate all my time</title>
      <dc:creator>nuttzzz_</dc:creator>
      <pubDate>Mon, 06 Jul 2026 11:36:59 +0000</pubDate>
      <link>https://dev.to/nuttzzz_/i-built-a-fitness-saas-in-nextjs-here-are-the-3-problems-that-ate-all-my-time-2p6m</link>
      <guid>https://dev.to/nuttzzz_/i-built-a-fitness-saas-in-nextjs-here-are-the-3-problems-that-ate-all-my-time-2p6m</guid>
      <description>&lt;p&gt;I'm a CS student, and for the last few months I've been building a fitness app in my spare time — the kind of thing where you tell yourself "it's just a workout logger" and three months later you've got offline sync, a food database, Stripe subscriptions and an AI coach. Classic scope creep, but I learned a lot.&lt;/p&gt;

&lt;p&gt;Here are the three things that actually ate my time, and how I ended up solving them. (Stack, if you care: Next.js 16 App Router, React 19, Prisma 7 + Postgres, Auth.js v5, Tailwind 4.)&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Offline workouts that don't lose your sets
&lt;/h2&gt;

&lt;p&gt;The gym I train at has basically no signal in the basement. So "log a set, hit save, watch it fail" was not an option — people would lose a whole workout and rage-quit the app.&lt;/p&gt;

&lt;p&gt;The approach that worked: every action (add set, edit weight, finish workout) becomes a mutation queued on the device, with an id generated on the client. When the network comes back, the queue flushes and syncs — idempotently, so replaying the same mutation twice doesn't duplicate anything. The key detail: "finish workout" doesn't resolve until everything in the queue has synced, so you never end up with a half-saved session.&lt;/p&gt;

&lt;p&gt;It sounds simple written down. It was not simple. Getting the App Router + a client-side queue + optimistic UI to all agree on the truth took more iterations than I'd like to admit.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Stripe, where the webhook is the only source of truth
&lt;/h2&gt;

&lt;p&gt;First version, I did the naive thing: user pays, redirect back, mark them Pro on the client. Don't do that. Any failed redirect or weird tab close and your billing state is a lie.&lt;/p&gt;

&lt;p&gt;I rewrote it so the webhook owns everything. &lt;code&gt;checkout.session.completed&lt;/code&gt; and the &lt;code&gt;customer.subscription.*&lt;/code&gt; events write &lt;code&gt;plan&lt;/code&gt; and &lt;code&gt;proUntil&lt;/code&gt; onto the user in the DB. The frontend never decides who's Pro — it just reads the DB. Suddenly all the edge cases (cancellations, failed renewals, refunds) handled themselves, because there was one place that knew the truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. An AI coach that costs cents, not a salary
&lt;/h2&gt;

&lt;p&gt;The premium feature is a weekly check-in: the app looks at your training volume, body-weight trend and diet adherence for the week, then suggests progressions and macro tweaks.&lt;/p&gt;

&lt;p&gt;The scary part was cost — an LLM per user per week sounds expensive. It isn't, if you're careful. A weekly cron builds a compact summary of the user's week (aggregated numbers, not raw data) and sends that to Claude Sonnet. It comes out to cents per user per week, so a $5/month sub covers it many times over. The trick was resisting the urge to send everything and instead sending the smallest payload that still gives a useful answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I did with it
&lt;/h2&gt;

&lt;p&gt;I built it as a real product first. But shipping another fitness app into a market with MyFitnessPal and Hevy felt like a bad bet — so I packaged the whole thing (offline sync, the Stripe setup, the AI coach, ~870 exercises, the food/barcode stuff) as a starter kit for other devs instead. Rebrand one file, deploy on a free Vercel + Neon stack.&lt;/p&gt;

&lt;p&gt;If you want to poke at it, there's a live demo — login &lt;code&gt;demo@atlas.app&lt;/code&gt; / &lt;code&gt;atlasdemo&lt;/code&gt;, it's got real-looking training history: &lt;a href="https://atlas-eta-ochre.vercel.app" rel="noopener noreferrer"&gt;https://atlas-eta-ochre.vercel.app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the kit is here if it's useful to you: &lt;a href="https://nuttzzz.gumroad.com/l/atlas" rel="noopener noreferrer"&gt;https://nuttzzz.gumroad.com/l/atlas&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Either way — happy to answer anything about the offline sync or the Stripe setup in the comments. That's the part I'd have killed for a write-up on when I started.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>nextjs</category>
      <category>react</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
