Node.js vs. Serverless Edge: Which Is Cheaper for MVPs?

A server rack connected by one continuous light path to a ring of edge execution points around a globe, representing always-on Node.js versus distributed edge compute

Choosing between an always-on Node.js process and a serverless edge runtime is a decision about who pays for your idle time, and at real early-MVP traffic - roughly 1-2 million requests a month - the three viable options land in genuinely different price brackets: a small always-on Node.js instance runs a flat $5-10/month regardless of load, AWS Lambda's request-and-duration billing can land under $3/month at that volume before you add API Gateway, and Cloudflare Workers' flat $5/month Paid plan covers up to 10 million requests outright. The right pick isn't universal - it depends on your traffic shape, not which platform your tutorial happened to use, and by the end of this post you'll have the actual billing formulas to run the numbers on your own traffic instead of guessing.


1. What's Actually Different Between a Node.js Server and an Edge Function?

A traditional Node.js deployment - a Docker container on Fly.io, Railway, or an EC2 instance - runs one persistent process that stays warm and holds state (in-memory caches, database connection pools, WebSocket connections) between requests, and you're billed for that process existing, not for each request it serves. A serverless edge function, whether it's a Cloudflare Worker or an AWS Lambda function, spins up a fresh execution context per request (or reuses a warm one opportunistically), has no guaranteed persistent memory between invocations, and bills you for compute actually consumed - but the two serverless options aren't interchangeable with each other either, because they run on fundamentally different runtimes.

Cloudflare Workers execute inside V8 isolates directly on Cloudflare's edge network, which is why cold starts are effectively eliminated - there's no full OS-level container to boot, just a lightweight JavaScript context. AWS Lambda's Node.js runtime, by contrast, runs inside a MicroVM, which is why a cold Lambda invocation still carries real, measurable startup latency that a Cloudflare Worker doesn't. That architectural difference is also why Workers don't get full Node.js API access by default - fs, several native modules, and raw TCP database connections either don't work or need Cloudflare's nodejs_compat flag and a service like Hyperdrive to work around, while Lambda's Node.js runtime is much closer to a normal Node process because it's genuinely running inside one.


2. Node.js Server vs. AWS Lambda vs. Cloudflare Workers: Which Fits Your MVP's Traffic Shape?

OptionCold StartNode.js API CompatibilityBilling Model
Node.js on a persistent server (Fly.io, Railway, EC2)None - process stays warmFull - it's a real Node processFlat fee for uptime, regardless of traffic
AWS Lambda (Node.js runtime)Real, measurable - MicroVM boot on cold invocationsFull - runs inside a real Node runtime$0.20/million requests + $0.0000166667/GB-second
Cloudflare Workers (V8 isolate)Effectively none - no MicroVM to bootPartial - needs nodejs_compat + Hyperdrive for DB access$5/month flat, includes 10M requests + 30M CPU-ms

As a Certified Project Manager, I don't scope this as a platform-wide decision - I scope it per endpoint. A webhook handler or auth check that's pure request/response logic with no persistent state is a strong Workers candidate; a route that needs a long-lived WebSocket connection, heavy CPU work, or deep AWS ecosystem integration (IAM, VPC, CloudWatch) stays on Lambda or a traditional Node server. Forcing one platform to handle both is how teams end up fighting their infrastructure instead of their product.


3. What Does This Actually Cost at Real Early-MVP Traffic?

Serverless Monthly Compute Cost (SMCC)

SMCC = (R × Cᵣ) + (G × Cᵍ)

R: requests per month, Cᵣ: cost per request
G: GB-seconds (or CPU-ms) consumed, Cᵍ: cost per unit

Run that formula against 1.5 million requests/month at 128MB memory and 150ms average execution - a realistic profile for an early API-driven MVP:

Always-on Node.js server (flat fee, independent of actual usage)
≈ $7/month, whether traffic is 10K or 1.5M requests
You're paying for the process existing 24/7, including the hours where it's serving a handful of requests overnight - predictable, but not usage-proportional.
AWS Lambda, Function URL (no API Gateway)
1.5M × $0.20/1M + 28,800 GB-s (under the 400K GB-s free tier) ≈ $0.30/month
At this exact volume, Lambda's request cost is nearly free and the compute cost falls entirely inside the perpetual free tier - the number changes fast if you route through API Gateway instead of a Function URL, which adds roughly $1.00-$1.40 per million requests on top.

Cloudflare Workers land differently: at 1.5M requests and well under 30M CPU-ms, you're inside the $5/month Paid plan's included allowance with no overage - a flat $5, not a usage-scaled number, which makes it the easiest of the three to budget against without modeling traffic at all.


4. What's the Actual Anti-Pattern Teams Fall Into Right Now?

❌ FOLLOWING AN OLD TUTORIAL'S 'runtime = edge'
// app/api/webhook/route.ts
export const runtime = 'edge'; // copied from a 2024 tutorial

export async function POST(req: Request) {
  // Full Node.js APIs (fs, many npm packages) aren't guaranteed here,
  // and this pattern is now deprecated on Vercel in favor of the
  // Node.js runtime with Fluid compute.
  const body = await req.json();
  return Response.json({ received: true });
}

Standalone Edge Functions are deprecated on Vercel as of 2026 - the platform's own guidance is to remove export const runtime = 'edge' entirely, since Node.js is now the default runtime and ships with Fluid compute and Active CPU pricing that already covers what Edge Functions used to offer.

✅ NODE.JS RUNTIME BY DEFAULT, EDGE FOR ROUTING ONLY
// app/api/webhook/route.ts - no runtime export needed
export async function POST(req: Request) {
  // Node.js is the default runtime: full API support, no directive required
  const body = await req.json();
  return Response.json({ received: true });
}

// middleware.ts still uses the Edge runtime by default - that part is
// correct and unaffected by the standalone Edge Functions deprecation
export function middleware(req: Request) {
  return Response.next();
}

Letting API routes default to the Node.js runtime and reserving the Edge runtime for what Vercel still recommends it for - Routing Middleware - matches the platform's current guidance instead of a pattern that's actively being phased out.


5. What Does Real Edge and Serverless Code Actually Look Like?

The Lambda handler below uses a Function URL instead of API Gateway specifically to avoid the per-million-request markup mentioned above - a real cost decision, not a style preference:

// lambda/webhook-handler.ts
import type { APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from 'aws-lambda';

export const handler = async (
  event: APIGatewayProxyEventV2
): Promise<APIGatewayProxyResultV2> => {
  try {
    const body = JSON.parse(event.body ?? '{}');
    if (!body.eventType) {
      // Fail fast with a real status code - Lambda Function URLs don't
      // give you API Gateway's request validation, so this check is on you now.
      return { statusCode: 400, body: JSON.stringify({ error: 'eventType is required' }) };
    }
    // ... process the event
    return { statusCode: 200, body: JSON.stringify({ received: true }) };
  } catch (err) {
    // Malformed JSON lands here instead of crashing the invocation silently
    return { statusCode: 400, body: JSON.stringify({ error: 'Invalid JSON body' }) };
  }
};

The Cloudflare Worker below hits the exact Node.js-incompatibility gotcha mentioned earlier - connecting to a Postgres database from a Worker isn't a raw pg connection, it goes through Hyperdrive:

// worker/db-query.ts
import postgres from 'postgres';

export default {
  async fetch(request: Request, env: { HYPERDRIVE: { connectionString: string } }) {
    // Workers can't hold a persistent TCP connection pool the way a
    // long-running Node process can - Hyperdrive fronts the actual
    // connection pooling so each isolate doesn't open its own connection.
    const sql = postgres(env.HYPERDRIVE.connectionString, { max: 5 });
    try {
      const rows = await sql`SELECT id, status FROM invoices WHERE tenant_id = ${request.headers.get('x-tenant-id')}`;
      return Response.json(rows);
    } catch (err) {
      return Response.json({ error: 'Query failed' }, { status: 500 });
    } finally {
      await sql.end({ timeout: 1 }); // release the pooled connection promptly
    }
  },
};

Diagram showing a user request reaching a routing decision point that sends lightweight endpoints to an edge worker and stateful endpoints to an origin Node.js server Figure 1: A single incoming request reaching one routing layer that sends lightweight, stateless endpoints to an edge worker and stateful or CPU-heavy endpoints to an origin Node.js server, within one hybrid architecture.


6. Conclusion and Actionable Roadmap

There's no universally cheaper option between a Node.js server, AWS Lambda, and Cloudflare Workers - there's only the option that matches your MVP's actual traffic shape and endpoint requirements. A steady, moderate-traffic app with stateful connections still comes out ahead on a flat-fee Node.js server; a spiky, low-average-traffic API comes out far cheaper on Lambda's near-free tier; and a latency-sensitive, globally distributed set of stateless endpoints is both the cheapest and the fastest option on Cloudflare Workers' flat $5/month plan. The actual mistake isn't picking the "wrong" one - it's picking one platform for every endpoint instead of running the billing formula above against each route's real profile.

Get your compute model matched to your actual traffic, not your tutorial's default: I architect hybrid Node.js/Lambda/Workers backends for SaaS MVPs, scoping each endpoint's runtime by its actual cost and latency profile rather than defaulting to one platform. Contact me today to book a 30-minute infrastructure cost audit.

Free Scoping Session

Have Something to Build?

Pick what you're trying to build below, and see exactly what a working engagement with me looks like - timeline, stack, and deliverables.

Product LaunchEst. Timeline: 4 to 8 Weeks

Build a SaaS MVP Roadmap

Turn your idea into a production-ready SaaS - architected, built, and shipped by one engineer, not a handoff chain.

Tech Stack

Next.js 16 + Tailwind v4 + PostgreSQL or MongoDB

Deliverables

Fully functional app with auth, billing, and database integrations.

Included With Your Scoping Call

MoSCoW-scoped feature list and a database architecture roadmap.

🔒 NDA Available⚡ Free scoping call, no obligation