<?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: HL</title>
    <description>The latest articles on DEV Community by HL (@hl05).</description>
    <link>https://dev.to/hl05</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%2F4081544%2F26956ead-b94e-4009-94ec-f73f549ca5e0.png</url>
      <title>DEV Community: HL</title>
      <link>https://dev.to/hl05</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hl05"/>
    <language>en</language>
    <item>
      <title>I tested my SaaS boilerplate against real Stripe, Supabase, and Resend accounts — here's what a "build passing" doesn't tell you</title>
      <dc:creator>HL</dc:creator>
      <pubDate>Mon, 17 Aug 2026 11:46:12 +0000</pubDate>
      <link>https://dev.to/hl05/i-tested-my-saas-boilerplate-against-real-stripe-supabase-and-resend-accounts-heres-what-a-2i1n</link>
      <guid>https://dev.to/hl05/i-tested-my-saas-boilerplate-against-real-stripe-supabase-and-resend-accounts-heres-what-a-2i1n</guid>
      <description>&lt;p&gt;Most boilerplates get shipped the moment &lt;code&gt;npm run build&lt;/code&gt; goes green. Mine almost did too — until I decided to actually verify it against live test accounts instead of trusting the compiler. That decision surfaced three real bugs that would have hit the very first real user, none of which TypeScript or a clean build ever flagged.&lt;/p&gt;

&lt;p&gt;Here's what I found, and why "it compiles" is a much weaker claim than it sounds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug 1: a Stripe API change that TypeScript didn't catch
&lt;/h2&gt;

&lt;p&gt;The webhook handler for &lt;code&gt;customer.subscription.updated&lt;/code&gt; read &lt;code&gt;subscription.current_period_end&lt;/code&gt; directly off the event payload:&lt;/p&gt;

&lt;p&gt;​&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;current_period_end&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="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current_period_end&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="s2"&gt;```



This type-checked fine and even worked when called via `&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;subscriptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="s2"&gt;` in a different code path. It crashed — `&lt;/span&gt;&lt;span class="nx"&gt;RangeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Invalid&lt;/span&gt; &lt;span class="nx"&gt;time&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="s2"&gt;` — only on the raw webhook event payload.

The reason: Stripe recently moved `&lt;/span&gt;&lt;span class="nx"&gt;current_period_end&lt;/span&gt;&lt;span class="s2"&gt;` off the top-level `&lt;/span&gt;&lt;span class="nx"&gt;Subscription&lt;/span&gt;&lt;span class="s2"&gt;` object and onto each `&lt;/span&gt;&lt;span class="nx"&gt;SubscriptionItem&lt;/span&gt;&lt;span class="s2"&gt;`, as part of their flexible billing / multiple-prices-per-subscription changes. A live SDK call to `&lt;/span&gt;&lt;span class="nf"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="s2"&gt;` apparently still exposes a compatibility value, but the raw JSON in a webhook event does not — it's just `&lt;/span&gt;&lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="s2"&gt;` there. `&lt;/span&gt;&lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="s2"&gt;` is `&lt;/span&gt;&lt;span class="kc"&gt;NaN&lt;/span&gt;&lt;span class="s2"&gt;`, and `&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="kc"&gt;NaN&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="s2"&gt;` throws.

TypeScript never caught it, either directly (my installed SDK's type definitions for `&lt;/span&gt;&lt;span class="nx"&gt;Subscription&lt;/span&gt;&lt;span class="s2"&gt;` still declared the field) or after I fixed it by reading `&lt;/span&gt;&lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&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="nx"&gt;current_period_end&lt;/span&gt;&lt;span class="s2"&gt;` instead — at that point the *types* hadn't caught up to the *API's* own change yet, and `&lt;/span&gt;&lt;span class="nx"&gt;SubscriptionItem&lt;/span&gt;&lt;span class="s2"&gt;` wasn't typed with the field even though the real API returns it. I only found this because I ran a real subscription through a real webhook, inspected the raw payload with `&lt;/span&gt;&lt;span class="nx"&gt;curl&lt;/span&gt;&lt;span class="s2"&gt;`, and watched it 500.

## Bug 2: a silent failure in auth, not a loud one

Supabase's built-in auth email sender is rate-limited by default — reasonably so, it's meant for early development, not production traffic. I hit that limit a few times while testing signup flows.

What surprised me: when the limit is hit, `&lt;/span&gt;&lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signUp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="s2"&gt;` doesn't just fail to send the confirmation email — it fails the entire signup, and *no user gets created at all*. No error visible in a cursory glance at the UI, nothing in the account. If I hadn't gone and checked via the admin API afterward, I'd have shipped a product where signups silently vanish above a low, undocumented threshold, until custom SMTP is configured.

## Bug 3: text you can't see, only sometimes

The landing page rendered fine — on my machine, in the theme I usually test in. A dark-mode system rendered the hero heading as dark navy text on a background that defaulted to near-black, because I'd never set an explicit `&lt;/span&gt;&lt;span class="nx"&gt;background&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="s2"&gt;` on `&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;`. `&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;gray&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;900&lt;/span&gt;&lt;span class="s2"&gt;` doesn't help you when the background isn't `&lt;/span&gt;&lt;span class="nx"&gt;white&lt;/span&gt;&lt;span class="s2"&gt;` — it's just as invisible as the wrong thing.

## What actually caught these

Not the compiler. Not `&lt;/span&gt;&lt;span class="nx"&gt;tsc&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;noEmit&lt;/span&gt;&lt;span class="s2"&gt;`. Not even a clean `&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt; &lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="s2"&gt;`, which passed the whole time bug #1 was live. What caught all three:

- Real test accounts on Stripe, Supabase, and Resend
- A real webhook listener (`&lt;/span&gt;&lt;span class="nx"&gt;stripe&lt;/span&gt; &lt;span class="nx"&gt;listen&lt;/span&gt;&lt;span class="s2"&gt;`) forwarding real events to a locally running server
- A real signup, a real test-card payment, real emails sent and inspected via the Resend API
- A screenshot taken in an actual browser, at actual default settings — not just "the dev server started without errors"

None of this is exotic tooling. It's the difference between "the code type-checks" and "the product works," and for anything selling itself as production-ready, that gap is exactly where the bugs a paying user finds tend to live.

---

If you're curious about the product this came out of — a B2B prospecting CRM template (Next.js 14, Supabase with RLS-enforced multi-tenancy, Stripe subscriptions + billing portal, Resend campaign emails with signed unsubscribe and webhook-based tracking) — it's for sale [here](https://elhugolouis.gumroad.com/l/ivnari), tested the way described above before I put a price on it.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>saas</category>
      <category>stripe</category>
      <category>webdev</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
