DEV Community

seller-mind
seller-mind

Posted on

What I Learned Building a SaaS Boilerplate From Scratch

When I first started building indie SaaS products, I had a terrible habit. I would get excited about a new idea, spin up a fresh Next.js repository, and then immediately get bogged down in the plumbing. I’d spend three days configuring NextAuth, another two wrestling with Stripe webhook signatures, and a whole weekend just trying to get my edge functions to deploy correctly. By the time the boilerplate was actually ready, my motivation was gone. I was struggling with the exact same setup tasks for every single project, losing my momentum to repetitive configuration. So I decided to build something.

The SaaS Setup Tax

The problem isn't that setting up billing and authentication is inherently difficult. It’s that it’s tedious and highly context-dependent. Every time I had to switch from building my core feature to debugging a CORS issue or a failing webhook, it took me at least twenty minutes to get back into the zone. That context switching is a silent killer for solo developers.

When you ignore this setup tax, the cost is measured in abandoned side projects. You lose the crucial early momentum when you're supposed to be validating your core idea. Instead of talking to users or building your unique value proposition, you're reading outdated Stripe documentation or configuring environment variables for the fifth time. I realized that to ship faster, I needed a unified, production-ready foundation that handled the boring stuff out of the box, specifically leveraging modern edge computing to keep things fast and cheap.

Architectural Decisions and Trade-offs

Building SaaSLaunch required making some tough architectural choices. I decided to go all-in on Next.js 14+ with the App Router. The transition from the Pages router was painful at first, especially with React Server Components, but the performance gains and simplified data fetching were worth the learning curve. The App Router's layout system also made it incredibly easy to structure the dashboard and the public marketing pages without duplicating navigation logic.

For authentication, NextAuth.js with OAuth providers was the clear choice to avoid reinventing the wheel. For billing, integrating Stripe subscriptions required careful handling of webhooks. I built a robust webhook handler that verifies signatures and updates the database atomically. It’s easy to get this wrong and end up with desynced user states, so I made sure the boilerplate includes idempotency checks out of the box.

The biggest lesson, however, came from how I handled API routes. Initially, I put everything in standard serverless functions. But I noticed cold starts and latency issues for global users. I decided to move specific heavy-lifting tasks, like web scraping and SEO analysis, to Cloudflare Workers. Running these at the edge drastically reduced latency. Here is a look at how the edge SEO analysis worker is structured:

// Cloudflare Worker for edge SEO analysis
export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    if (url.pathname !== '/api/analyze') {
      return new Response('Not found', { status: 404 });
    }

    const targetUrl = url.searchParams.get('url');
    if (!targetUrl) {
      return new Response(JSON.stringify({ error: 'Missing url' }), {
        status: 400,
        headers: { 'Content-Type': 'application/json' },
      });
    }

    // Fetch target page at the edge (low latency worldwide)
    const response = await fetch(targetUrl, {
      cf: { cacheTtl: 300, cacheEverything: true },
    });
    const html = await response.text();

    // Run SEO checks (regex-based, no DOM needed)
    const title = html.match(/<title[^>]*>([^<]*)<\/title>/i)?.[1] || '';
    const desc = html.match(/<meta[^>]*name=["']description["'][^>]*content=["']([^"']*)["']/i)?.[1] || '';
    const h1Count = (html.match(/<h1[ >]/gi) || []).length;

    return new Response(JSON.stringify({ title, desc, h1Count }), {
      headers: { 'Content-Type': 'application/json' },
    });
  },
};
Enter fullscreen mode Exit fullscreen mode

This worker intercepts requests, fetches the target HTML directly at the edge with Cloudflare's caching rules, and runs lightweight regex checks. No heavy DOM parsing is needed, which keeps the worker execution time well under the free tier limits. It’s a perfect example of why moving specific tools to the edge makes sense for a SaaS boilerplate.

What Worked and What I'd Change

What surprised me most was the feedback on the TypeScript-first approach. Initially, I thought about offering a JavaScript version to lower the barrier to entry, but enforcing strict types across the Stripe and NextAuth configurations prevented so many subtle bugs during my own testing. I also realized that one-click deployment to Vercel or Cloudflare Pages isn't just a nice-to-have; it's essential for developer experience. Configuring environment variables across different platforms can be a headache, so abstracting that into a single deploy script was a major win.

If I were to do it again, I would change how I initially over-engineered the Stripe webhook handling. I tried to account for every possible edge case upfront, which made the code unreadable. I’ve since simplified it to handle just the core events needed for a standard subscription model, keeping the codebase much cleaner.

Checking Your Own SEO

If you're working on a SaaS and want to quickly check your landing page's SEO before launching, I extracted the edge tool from the boilerplate into a standalone free resource. You can use the SEO Checker at https://saaslaunch.dev/free-tools/seo-checker to analyze your title tags, meta descriptions, and H1 structures instantly without needing to deploy anything.


Full disclosure: I'm the developer of SaaSLaunch. I built this because I needed it myself.

Top comments (0)