What I Learned Building a Next.js SaaS Starter with Edge Workers
When I first started building my own SaaS products, I kept running into the same frustrating loop. I would have a great idea, set up a fresh repository, and then spend the next two weeks just wiring up the boring stuff.
When I first started building my own SaaS products, I kept running into the same frustrating loop. I would have a great idea, set up a fresh repository, and then spend the next two weeks just wiring up the boring stuff. Configuring Stripe checkout sessions, setting up NextAuth.js with OAuth providers, and writing webhook handlers. By the time I actually started writing code for my unique product features, my momentum was completely gone. I was struggling with the same boilerplate over and over. So I decided to build something.
The Problem with SaaS Boilerplate
The problem with SaaS boilerplate isn't just that it takes time to write; it's the context switching. Every time you jump into a new project and have to remember how to properly verify a Stripe webhook signature or configure edge caching, you lose focus. The cost of ignoring this early on is high. You end up hacking together quick fixes that turn into technical debt. Later, when you need to scale or add a new pricing tier, you realize your auth middleware is tangled up with your billing logic. Building a solid foundation from day one isn't just about saving a few hours; it's about preserving your mental energy for the actual product.
Architecting the Solution
To solve this for myself, I started architecting a production-ready SaaS starter kit, which eventually became SaaSLaunch. I chose Next.js 14 with the App Router for the core framework. Being TypeScript-first was non-negotiable for me; having full type safety across the entire stack, from the database schemas to the frontend components, prevents so many runtime errors. For billing, I integrated Stripe subscription flows directly into the API routes, handling both the checkout session creation and the webhook verification.
But the real game-changer for me was moving some of the heavier API logic to the edge using Cloudflare Workers. I wanted to build built-in SEO tools for the starter kit, specifically a landing page analyzer. Running this on standard serverless functions meant dealing with cold starts and higher latency for users around the world. By moving it to Cloudflare Workers, I could fetch and analyze HTML right at the edge.
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' },
});
},
};
Let's break down what's happening here. First, we validate the incoming request and extract the target URL from the query parameters. The magic happens in the fetch call. By passing the cf object with cacheTtl and cacheEverything, we instruct Cloudflare to cache the response at the edge for 300 seconds. This means if multiple users check the same URL, we don't hammer the target server.
For the actual analysis, I opted for regex-based parsing instead of loading a heavy DOM parser into the Worker environment. Workers have strict memory limits, and keeping the footprint small ensures the function executes in milliseconds. We extract the title, meta description, and count the H1 tags, then return a clean JSON response.
Practical Results and Trade-offs
Building this architecture taught me a lot about the trade-offs between traditional serverless and edge computing. What worked exceptionally well was the latency reduction. The edge SEO checker feels instantaneous, which makes the dashboard experience much smoother. I was also pleasantly surprised by how well the Cloudflare cf cache options integrate directly into the standard fetch API. It requires zero extra configuration and just works out of the box. One-click deployment to Vercel for the Next.js frontend and Cloudflare Pages for the edge workers made the CI/CD pipeline incredibly simple to set up.
If I were to change anything about the initial build, I would probably abstract the Stripe webhook handling a bit more in the starter kit. Right now, it works perfectly for standard subscription events, but handling complex edge cases like prorations or failed payment retries requires careful routing. I've since refactored that part to use a more robust event dispatcher pattern to keep the API routes clean.
Try It Yourself
If you are working on a SaaS project and want to see how the edge SEO analysis works in practice, I put together a free tool using this exact logic. You can test your landing pages and see the raw edge response times yourself at the SaaSLaunch free SEO checker.
Full disclosure: I'm the developer of SaaSLaunch. I built this because I needed it myself.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.