Comparing Memory Limits: Netlify vs Vercel Edge

Netlify Edge Functions and Vercel Edge Middleware both execute JavaScript in isolated runtimes, but their memory limits, garbage collection behavior, and error signatures differ. Knowing the exact limits and failure modes helps you write code that stays within budget on both platforms.

This guide is part of Memory and CPU Limits Across Edge Providers. Where the parent guide covers all three providers, this one drills into the 128 MB versus 512 MB decision between Netlify and Vercel; if your constraint is CPU rather than memory, see avoiding CPU time-limit errors in Cloudflare Workers.

Vercel 128 MB versus Netlify 512 MB memory budgets Vercel Edge gives 128 MB total with roughly 20 to 40 MB runtime overhead, while Netlify Edge gives 512 MB; both terminate immediately on overflow. Vercel Edge Middleware overhead 20–40 MB usable 88–108 MB 128 MB cap Netlify Edge Functions usable ~470–490 MB 512 MB cap Both: overflow → immediate termination (502/503/504)
Vercel Edge leaves 88–108 MB usable after runtime overhead within its 128 MB cap; Netlify's 512 MB is far more generous but still terminates hard on overflow.

Memory Limits

Platform Memory Limit Runtime GC Model
Vercel Edge Middleware 128 MB V8 isolate Aggressive GC; OOM terminates immediately
Netlify Edge Functions 512 MB Deno/V8 hybrid Per-isolate GC; pool-level pressure possible

Vercel’s 128 MB cap applies to the total resident memory of the isolate: runtime engine overhead, bundled code, heap allocations, and network buffers. Baseline overhead from the runtime and framework bootstrapping consumes roughly 20–40 MB before your handler runs, leaving 88–108 MB for request processing.

Netlify’s 512 MB limit is generous relative to Vercel, but Netlify’s Deno-based worker pool means that high concurrency under memory pressure can cause pool-wide degradation. Individual isolates that accumulate cache or header bloat without explicit cleanup can trigger pool restarts that are not visible at the individual function level.

Identifying OOM Failures

Vercel Edge Runtime:

  • Log messages: Memory limit exceeded or Function crashed with out of memory (OOM)
  • HTTP behavior: Immediate 502 or 504 with no application-level error body
  • Pattern: OOM often occurs during initialization (polyfill loading, large module parse) before business logic runs

Netlify Edge Runtime:

  • Log messages: Runtime exceeded memory limit or Function execution failed
  • HTTP behavior: 502 or 503; Deno process restarts without explicit stack traces
  • Pattern: Shared worker pool exhaustion causes cascading timeouts when multiple functions accumulate memory

Primary triggers on both platforms:

  • Synchronous JSON.parse() on large payloads (entire body materialized in heap at once)
  • Array.concat() or Buffer.concat() accumulating chunks in a loop
  • Heavy polyfill bundles inflating baseline heap before the handler executes
  • Unbounded Cache API writes without size limits

Step 1: Strip Unnecessary Headers Before Processing

Headers consume isolate memory synchronously. Each header key-value pair is allocated in the V8 heap before your handler code runs. Strip headers not needed by your business logic:

// memory-safe-header-filter.ts
const FORWARDED_HEADERS = new Set([
  'content-type',
  'authorization',
  'x-request-id',
  'cache-control',
  'accept',
]);

export function sanitizeRequestHeaders(headers: Headers): Headers {
  const filtered = new Headers();
  for (const [key, value] of headers.entries()) {
    if (FORWARDED_HEADERS.has(key.toLowerCase())) {
      filtered.set(key, value);
    }
  }
  return filtered;
}

Step 2: Stream JSON Instead of Buffering

Synchronous JSON.parse() materializes the entire payload as a string in heap, then again as a parsed object. For payloads over a few hundred kilobytes this can double your memory usage. Use chunked reading with a buffer cap:

export async function streamJsonResponse<T>(response: Response, maxBytes = 2_000_000): Promise<T> {
  if (!response.body) throw new Error('Empty response body');

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let buffer = '';

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    buffer += decoder.decode(value, { stream: true });

    if (buffer.length > maxBytes) {
      reader.cancel();
      throw new Error(`Payload exceeds ${maxBytes} byte streaming buffer`);
    }
  }

  if (!buffer) throw new Error('Empty payload');
  return JSON.parse(buffer) as T;
}

This does not eliminate the final JSON.parse heap spike, but it prevents unbounded accumulation and gives you a predictable failure mode (exception) rather than an OOM kill.

Step 3: Configure Platform Runtime Flags

Vercel (vercel.json) — edge middleware does not support custom memory configuration; 128 MB is fixed. For Serverless Functions that need more headroom:

{
  "functions": {
    "app/api/**/*.ts": {
      "maxDuration": 10,
      "memory": 512
    }
  }
}

Note: memory and maxDuration apply only to Serverless Functions, not Edge Middleware. Edge Middleware always runs with 128 MB and the 1000 ms wall-clock limit.

Netlify (netlify.toml) — configure edge function routing and bundler options:

[build]
  command = "npm run build"

[[edge_functions]]
  path = "/api/*"
  function = "api-handler"

[functions]
  node_bundler = "esbuild"

There is no memory_limit key in Netlify’s netlify.toml for edge functions. Memory allocation is fixed at 512 MB by the platform.

Step 4: Memory-Safe Cache Writes

Unbounded Cache API writes exhaust heap. Each cached response body is serialized into V8 heap before being committed:

const MAX_CACHEABLE_BYTES = 5_000_000; // 5 MB

export async function cacheWithSizeLimit(
  cacheName: string,
  request: Request,
  response: Response
): Promise<void> {
  const contentLength = parseInt(response.headers.get('content-length') ?? '0', 10);

  if (contentLength > MAX_CACHEABLE_BYTES) {
    console.warn('Skipping cache: payload too large for edge memory budget');
    return;
  }

  const cache = await caches.open(cacheName);
  const ttlResponse = new Response(response.body, {
    headers: {
      ...Object.fromEntries(response.headers.entries()),
      'cache-control': 'public, max-age=300',
    },
  });
  await cache.put(request, ttlResponse);
}

Step 5: Structured Memory Logging

Add lightweight memory telemetry to track payload sizes through your handler. Edge runtimes do not expose process.memoryUsage(); use payload size as a proxy:

export function logPayloadMetrics(context: string, payloadBytes: number) {
  console.log(JSON.stringify({
    level: payloadBytes > 10_000_000 ? 'warn' : 'info',
    context,
    payloadBytes,
    runtime: typeof Deno !== 'undefined' ? 'deno' : 'v8-edge',
    ts: new Date().toISOString(),
  }));
}

Simulating Production Memory Constraints Locally

Neither vercel dev nor netlify dev enforces memory limits. To simulate 128 MB behavior locally:

# Cap Node.js heap to match Vercel edge isolate
node --max-old-space-size=128 dist/server.js

For Netlify’s 512 MB limit:

node --max-old-space-size=512 dist/server.js

Run your load tests against these processes to identify handlers that approach the memory ceiling before they cause OOM kills in production.

Local vs Production Divergence

Concern Local (vercel dev / netlify dev) Production
Memory cap enforced No Yes — 128 MB (Vercel) / 512 MB (Netlify)
OOM failure mode Process grows until host OOM Immediate isolate termination, 502/503
Runtime baseline overhead Differs from production isolate 20–40 MB consumed before handler
Worker-pool pressure Not reproduced Netlify pool restarts under concurrency
Reliable budget signal Only under --max-old-space-size Yes

Validate the Streaming Guard with Vitest

Assert that the buffered-read helper throws a predictable exception rather than letting an oversized payload OOM the isolate:

// stream-json.test.ts
import { describe, it, expect } from 'vitest';
import { streamJsonResponse } from './stream-json';

function responseFrom(bytes: number): Response {
  const stream = new ReadableStream<Uint8Array>({
    start(controller) {
      controller.enqueue(new Uint8Array(bytes));
      controller.close();
    },
  });
  return new Response(stream);
}

describe('streamJsonResponse', () => {
  it('throws before exceeding the byte cap', async () => {
    await expect(streamJsonResponse(responseFrom(3_000_000), 2_000_000)).rejects.toThrow(
      /streaming buffer/
    );
  });

  it('parses a payload under the cap', async () => {
    const body = JSON.stringify({ ok: true });
    await expect(streamJsonResponse(new Response(body), 2_000_000)).resolves.toEqual({
      ok: true,
    });
  });
});

Named Pitfalls

  • Trusting vercel dev memory behavior — it never enforces 128 MB; only node --max-old-space-size=128 approximates the cap.
  • Synchronous JSON.parse on large bodies — materializes the payload twice in heap; stream with a byte cap instead.
  • Forwarding every request header — each key-value pair is allocated before your handler runs; strip to an allow-list.
  • Unbounded Cache API writes — serialize each body into heap before commit; guard with a content-length size check.
  • Assuming a healthy Netlify function is isolated — pool-wide pressure can restart neighbors; cap per-request memory even with 512 MB headroom.

Production Deployment Checklist

  • Large payloads read with a byte-capped streaming helper, not
  • Cache API writes guarded by a content-length
  • Load tested under --max-old-space-size=128 (Vercel) and =512
  • OOM log strings monitored: Memory limit exceeded /

Frequently Asked Questions

How much of Vercel's 128 MB is actually usable?

Roughly 88–108 MB. The runtime engine and framework bootstrapping consume about 20–40 MB before your handler runs, and that overhead counts against the same 128 MB cap as your heap allocations and network buffers.

Can I raise the memory limit on Vercel Edge Middleware?

No. The 128 MB cap and 1000 ms wall-clock are fixed for Edge Middleware. The memory and maxDuration keys in vercel.json apply only to Serverless Functions, where you can request up to 512 MB or more.

Why does a healthy Netlify function still time out under load?

Netlify’s Deno-based worker pool is shared. When several functions accumulate memory without cleanup, pool-wide pressure can trigger restarts that cascade into timeouts, even though no single function exceeded 512 MB on its own.

What is the safest way to handle large JSON at the edge?

Read the response body in chunks with a byte cap and reject anything over the limit before parsing. This converts an unpredictable OOM kill into a catchable exception, though the final JSON.parse still produces a heap spike you must budget for.

How do I get a reliable memory signal without `process.memoryUsage()`?

Edge runtimes do not expose process.memoryUsage(), so log payload byte sizes as a proxy and run load tests under node --max-old-space-size=128 (or 512 for Netlify) to find handlers that approach the ceiling before they fail in production.

Conclusion

Vercel’s 128 MB cap is the binding constraint for edge middleware; Netlify’s 512 MB is more permissive but not unlimited. The most common causes of OOM—synchronous large payload buffering, unbounded cache writes, and heavy polyfill bundles—are addressable with streaming patterns, cache size limits, and dependency auditing. When memory requirements genuinely exceed 128 MB, either restructure the workload to stream data without buffering it, or route to Netlify’s more permissive environment or a regional serverless function.