DEV Community

seller-mind
seller-mind

Posted on

How I Built a SaaS Starter Kit to Stop Repeating Myself

When I first started building SaaS projects, I’d spend the first two weeks just wiring up the boring stuff. Authentication, Stripe checkout, webhook handlers, SEO meta tags. It felt like every new idea required me to rebuild the same foundation from scratch. I’d get excited about a product concept, open my editor, and then spend three days debugging a NextAuth configuration or figuring out why a Stripe webhook was failing in production. Eventually, the friction would kill my momentum. The setup phase was always the most tedious part of the journey. So I decided to build something.

The problem with "just using a boilerplate" is that most of them are outdated the moment they are published. Frameworks move incredibly fast. Next.js shifted to the App Router, Stripe updates their API, and edge computing changes how we think about latency. When you ignore these shifts and stick to legacy setups, you end up with technical debt before you even write your first line of business logic. The cost of ignoring this is high. You lose weekends to context switching, your app suffers from unnecessary latency because everything is routed through a single region, and you eventually abandon the project because the plumbing took longer than the actual product development.

I wanted a stack that was modern, type-safe, and fast. I chose Next.js 14 with the App Router for the frontend and core API, NextAuth.js for authentication, and Stripe for billing. But the real game-changer for me was moving specific workloads to the edge using Cloudflare Workers.

Instead of routing every request through a central serverless function, I offloaded tasks like SEO analysis to the edge. This keeps the main Next.js deployment lightweight and reduces latency for users globally.

Here is a look at how I structured the Cloudflare Worker for the SEO analysis tool:

// 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

In this worker, I intercept requests to the /api/analyze path. Instead of loading a heavy DOM parser on the server, I fetch the target URL directly at the edge. I use Cloudflare's caching features to keep latency low, and then run simple regex checks against the raw HTML. This extracts the title, meta description, and H1 count without needing a full browser environment.

For the core SaaS features, I also had to rethink how Stripe webhooks are handled in the App Router. Moving away from Pages router API routes meant adapting to the new Request/Response objects, but it ultimately resulted in cleaner, more predictable code. The TypeScript-first approach ensured that my Stripe types and NextAuth session types were strictly enforced across the entire codebase.

Putting this together into a cohesive starter kit completely changed how I build. Having Stripe subscription billing with checkout and webhook handling already wired up saved me countless hours of debugging payment edge cases. The one-click deployment to Vercel or Cloudflare Pages meant I could go from zero to a live, production-ready URL in minutes, rather than wrestling with CI/CD pipelines.

What surprised me the most was the performance of the edge SEO checker. Running it at the edge meant it could fetch and analyze pages from anywhere in the world in milliseconds. A traditional serverless function routed through a single US region would always have a baseline latency penalty for international users, but the edge worker eliminated that bottleneck entirely.

If I were to iterate on this again, I’d probably expand the edge caching strategies. Right now, the regex parsing is great for speed, but HTML can sometimes be messy or malformed. Adding a fallback to a lightweight HTML parser for edge cases would make it more robust. I'd also love to add more granular error handling for the regex parsing to ensure we don't return partial data if a tag is unexpectedly closed.

If you're curious about how this edge setup performs in the real world, I've deployed the SEO checker as a free tool. You can test it out and analyze your own landing pages right here: https://saaslaunch.dev/free-tools/seo-checker. It's a great way to see the edge worker in action without having to set up the whole stack yourself.


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

Top comments (0)