When I first started building my SaaS, I threw everything into a single, centralized serverless setup. It was incredibly easy at first. All my API routes, background tasks, and third-party integrations lived in the same Next.js project. But as my user base grew globally, I started noticing latency creeping up on some of my heavier background tasks, particularly an HTML parsing tool I had built.
I was struggling with the trade-off between the simplicity of a monolithic serverless setup and the raw performance of edge computing. I wanted the developer experience of a unified codebase, but I needed the global low-latency execution for specific tools. So I decided to build something to bridge that gap.
The Problem with "One-Size-Fits-All" SaaS Architecture
When you are bootstrapping a SaaS, it is tempting to just deploy everything to a single platform. Centralized serverless functions (like standard Vercel functions) are fantastic. They integrate seamlessly with your frontend framework, handle database connections well, and give you a great developer experience.
However, they run in specific regions. If your SaaS has global users performing heavy tasks—like parsing large HTML documents for SEO analysis, or processing complex webhooks—regional latency and cold starts become real bottlenecks.
On the flip side, going 100% edge for everything (like running all your logic on Cloudflare Workers) introduces its own headaches. Edge environments are incredibly fast, but they have strict limitations. You often cannot use native Node.js modules, execution times are strictly capped, and managing persistent database connections directly from the edge can be tricky.
The cost of ignoring this architectural divide? You either end up with a sluggish global experience for your users, or a highly fragmented codebase where you are manually managing two completely different deployment targets and languages.
A Hybrid Technical Approach
To solve this, I compared three approaches: keeping everything in a centralized serverless environment, moving everything to the edge, or adopting a hybrid model.
I ultimately chose the hybrid approach for SaaSLaunch. The core application logic—handling Stripe subscription billing, NextAuth.js authentication, and heavy database operations—stays in the Next.js 14+ App Router. These tasks benefit from the robust Node.js environment and longer execution timeouts.
Meanwhile, I moved the global-facing, read-heavy tools to Cloudflare Workers. This includes the built-in SEO checker and landing page analyzer. By running these at the edge, the code executes in a data center physically close to the user, drastically reducing latency.
Here is what the edge API route for the SEO analysis looks like under the hood:
// 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 how we use the cf object in the fetch request. This tells the Cloudflare network to cache the response for 300 seconds, which saves processing power if multiple users check the same URL. Also, because we are at the edge, we avoid pulling in a heavy DOM parsing library like jsdom. Instead, we rely on lightweight regex matching to extract the title, meta description, and H1 count. This keeps the worker execution time well under the strict limits.
Practical Results and Trade-offs
What worked exceptionally well was the TypeScript-first approach. Because the entire SaaSLaunch boilerplate is built with full type safety, I could share interfaces and types between the Next.js application and the Cloudflare Workers. Moving logic between the two environments felt seamless rather than like I was writing in two different dialects.
I was also pleasantly surprised by the deployment experience. Despite splitting the architecture, the boilerplate supports one-click deployment to either Vercel (for the Next.js app) or Cloudflare Pages (for the edge tools). You do not have to manually configure complex CI/CD pipelines to get the hybrid setup running.
However, there were trade-offs. Initially, I tried to put the Stripe webhook handling into the edge worker to keep things unified. It was a mistake. The strict execution time limits and the lack of certain Node.js cryptographic modules in the edge runtime made verifying Stripe signatures a headache. I quickly moved the webhook handling back to the standard Next.js API routes. It is a good reminder that edge computing is powerful, but it is not a magic bullet for every single backend task.
Testing Your Own SaaS Setup
If you are currently building a SaaS and want to ensure your landing page is optimized before you launch, you can test it using the exact edge architecture I described above.
I built a free SEO checker that runs on these Cloudflare Workers to give you a fast, global analysis of your page's core SEO elements. You can check it out here: SEO Checker.
Full disclosure: I'm the developer of SaaSLaunch. I built this because I needed it myself.
Top comments (0)