DEV Community

seller-mind
seller-mind

Posted on

How I Architected a Next.js SaaS with Edge-First APIs

When I first started building my SaaS, I spent three weeks just wiring up authentication, Stripe webhooks, and basic API routes. Every time I tried to add a new feature, I was bottlenecked by latency in my serverless functions and wrestling with cold starts. I was staring at a blank terminal, trying to configure local webhook testing with ngrok, and realizing I was spending 80% of my time on infrastructure plumbing and only 20% on the actual product.

The traditional SaaS stack often relies on a centralized database and serverless functions that can suffer from cold starts. When your API routes are hosted in a single region, users on the other side of the world experience noticeable lag. For a SaaS, especially one with global ambitions, this latency kills the user experience. Furthermore, setting up billing, auth, and edge routing from scratch every time is a massive context switch that pulls you away from solving your users' actual problems.

I wanted a setup that was fast globally, type-safe from the database to the frontend, and didn't require me to maintain five different microservices just to get a basic product out the door.

So I decided to build something.

The Architecture Decisions

For the core application, I chose Next.js 14+ with the App Router. The App Router's React Server Components allow you to fetch data directly on the server, reducing the client-side JavaScript bundle. For authentication, NextAuth.js with OAuth providers handled the heavy lifting of session management without me having to write custom JWT logic.

Stripe subscription billing was integrated directly into the Next.js API routes. Keeping the checkout session creation inside the App Router meant I could leverage server-side environment variables securely without exposing keys to the client.

But the biggest architectural shift was moving specific API routes to the edge using Cloudflare Workers.

Why Edge Computing for SaaS APIs?

Traditionally, if you need to run a background task or process an external URL, you spin up a separate backend service or rely on a heavy serverless function. But edge computing allows you to run code in data centers all over the world, physically closer to your users.

I needed to build a built-in SEO checker and landing page analyzer. Fetching a target URL, parsing the HTML, and returning the metadata can be slow if the server is in Virginia and the target site is in Tokyo. By moving this logic to Cloudflare Workers, the fetch happens at the edge, drastically reducing latency.

Here is how the edge worker for the SEO analysis 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

Trade-offs and Implementation Details

Notice that the HTML parsing relies on regex rather than a DOM parser like jsdom. In an edge environment, you want to keep your worker bundle as small and execution as fast as possible. Loading a full DOM parser adds unnecessary weight and cold-start time. For basic SEO metrics like title, description, and H1 counts, regex is surprisingly effective and keeps the worker lightweight.

Another key decision was keeping the entire codebase TypeScript-first. Having full type safety across the Next.js frontend, the Stripe integration, and the Cloudflare Workers meant that refactoring the data models didn't result in runtime errors.

Practical Results

What worked exceptionally well was the deployment experience. Because the stack is designed for modern infrastructure, deploying to Vercel for the main Next.js app and Cloudflare Pages for the edge tools was a one-click process. The separation of concerns between the core app and the edge workers kept the repository clean.

What surprised me was how little code was actually required for the Cloudflare Workers compared to spinning up a whole separate backend service. The edge environment handles routing, caching, and scaling out of the box. The latency improvements for the SEO checker were immediately noticeable during testing.

If I were to do it again, I would simplify the webhook handling earlier in the process. Initially, I over-engineered the Stripe webhook retries and database state updates. I spent days building a complex queue system before realizing that for my initial launch, a simple, idempotent database check inside the webhook route was more than sufficient.

Trying It Out

If you are building a SaaS and want to see how edge computing handles SEO analysis in practice, I put together a free tool to demonstrate the architecture.

You can test your own landing pages and see the edge analysis in action here: SEO Checker

Building a SaaS is hard enough without fighting your infrastructure. Hopefully, sharing these architectural decisions helps you spend less time wiring up APIs and more time building features your users actually want.


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

Top comments (0)