When I first started building tool-based SaaS products, I relied entirely on standard serverless functions for everything. It was the default approach, and it worked fine for standard CRUD operations and basic API endpoints. But when I started building an SEO analysis tool that needed to fetch and parse external web pages, I hit a wall. The latency was noticeable. A user in London would trigger a function in US-East, wait for the environment to initialize, and then wait for the function to fetch a European website. It felt sluggish. I tried tweaking timeouts and optimizing the code, but the physical distance was the real bottleneck. So I decided to build something.
The Problem with Centralized Serverless
The core issue with relying solely on traditional serverless architectures for global, network-bound SaaS tools is regional latency. When your application's core value proposition involves interacting with the public web—like analyzing landing pages, checking meta tags, or scraping data—where that code executes matters immensely.
Traditional serverless platforms typically route requests to a centralized region or a small cluster of regions. If your user is in Asia and your function is in North America, you are adding hundreds of milliseconds of network transit time before your code even begins executing. For a quick database read, this is often acceptable. But for a tool that needs to fetch an external URL, parse the HTML, and return a result, this latency compounds.
The cost of ignoring this architectural mismatch is a degraded user experience. In the SaaS world, especially for developer or marketing tools, a sluggish interface directly impacts user retention and perceived quality. Users expect instant feedback when they submit a URL for analysis, and if your architecture adds unnecessary physical distance to that request, you lose that immediacy.
Comparing Architectural Approaches
To solve this, I had to objectively compare two different execution environments: traditional serverless (like standard Next.js API routes) and edge computing (like Cloudflare Workers).
Traditional Serverless (Next.js API Routes)
Serverless functions are excellent for heavy compute, complex database transactions, and secure backend logic. This is where I handle Stripe billing and webhook processing. It has access to the full Node.js environment, meaning you can use heavy libraries, complex DOM parsers, and large dependencies. The trade-off is that it usually runs in fewer regions, leading to higher latency for global users, and you can sometimes face cold starts depending on the provider.
Edge Computing (Cloudflare Workers)
Edge functions run in hundreds of locations globally, milliseconds away from the user. The trade-off is strict limits on CPU time, memory, and bundle size. You cannot easily run heavy Node.js native modules or massive DOM parsing libraries. You have to write lean, optimized code and think carefully about your dependencies.
The Architecture Decision
I decided to split the workload based on the strengths of each environment. I kept the heavy, stateful operations—like Stripe checkout and authentication—in the Next.js serverless environment. For the network-bound, read-heavy SEO analysis, I moved the logic to Cloudflare Workers.
Because I couldn't use a heavy DOM parser at the edge without bloating the bundle or hitting CPU limits, I had to adapt. I switched to lightweight, regex-based parsing. It’s not as robust as a full DOM tree traversal, but for extracting basic SEO metrics like titles, meta descriptions, and H1 counts, it is incredibly fast and perfectly sufficient for the use case.
Here is what the edge worker looks like in practice:
// 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' },
});
},
};
Notice the cf object in the fetch options. This allows us to leverage Cloudflare's cache at the edge. If multiple users analyze the same URL in a short window, we don't even have to make the external request again. This is a substantial performance win that is much harder to configure in standard serverless environments.
Practical Results and Trade-offs
Splitting the architecture yielded immediate benefits. The global latency for the SEO checker dropped significantly. Users in Europe and Asia reported a much snappier experience compared to the old centralized serverless setup.
What surprised me was how little we actually lost by dropping the heavy DOM parser. The regex approach handled the vast majority of standard web pages well, and for the edge cases where it failed, the speed trade-off was entirely worth it.
If I were to do this again from day one, I would structure the boilerplate to separate edge and serverless routes more cleanly from the start, rather than migrating it later. Setting up the TypeScript types to bridge the edge environment and the main Next.js app took a bit of extra configuration, but the performance payoff was undeniable.
Try It Yourself
If you want to see this edge architecture in action without building it from scratch, I put together a free SEO checker that runs on this exact Cloudflare Worker setup. You can test it out and see the response times for yourself at https://saaslaunch.dev/free-tools/seo-checker.
Full disclosure: I'm the developer of SaaSLaunch. I built this because I needed it myself.
Top comments (0)