Memory Explosion in Next.js API Routes

We’re experiencing a critical memory management issue with our Next.js API routes on Vercel that’s causing 504 Gateway Timeout errors and inconsistent performance. Despite implementing proper memory management patterns including request-scoped clients, garbage collection, and cleanup procedures, our API calls are showing unpredictable memory behavior that appears to be platform-related rather than application code issues.

The DB is really quite small right now, just can’t figure a solution out for this. This was working perfectly on localhost.

The problem manifests as memory explosions during API execution - requests start with normal memory usage (20MB) but then spike to nearly the maximum limit (2028MB out of 2048MB) during the same request execution, ultimately causing timeouts. This inconsistent behavior suggests a potential issue with Vercel’s serverless memory management or garbage collection system.

Primary Issue:
Next.js API routes (/api/get-user-analyses) show memory explosion during execution
Initial memory: 20MB (normal baseline)
Peak memory: 2028 MB (99% of 2048 MB limit) during the same request
Memory grows from 20MB to 2GB within a single API call execution
Results in 504 Gateway Timeout after 30 seconds

API calls start with normal memory usage (20MB)
Memory explodes to 2028MB (99% of 2048MB limit) during execution
Functions timeout after 30 seconds with 504 Gateway Timeout errors. Auth calls with same Supabase database work fine.

API Route Code:
// app/api/get-user-analyses/route.ts
export async function POST(request: Request) {
const requestId = crypto.randomUUID()
const startTime = Date.now()
const beforeMemory = process.memoryUsage().heapUsed

console.log([Get User Analyses API] Starting request ${requestId})
console.log([Get User Analyses API] Initial memory: ${Math.round(beforeMemory / 1024 / 1024)}MB)

try {
const { userId, page = 1, pageSize = 50 } = await request.json();

// Request-scoped client
const supabase = await getServerSideSupabase(requestId);

// Simple database query
const { data: allUploads, error: allError, count } = await supabase
  .from('uploads')
  .select(`id, file_name, created_at, status`, { count: 'exact' })
  .eq('user_id', userId)
  .order('created_at', { ascending: false })
  .range(from, to);
return NextResponse.json({ analyses: allUploads });

} catch (error) {
console.error([Get User Analyses API] Request ${requestId} failed:, error);
return NextResponse.json({ error: “Internal server error” }, { status: 500 });
} finally {
// Cleanup
resetServerClient(requestId)
if (global.gc) global.gc()
}
}
Client Management Code:
// lib/supabase/optimized-clients.ts
const requestClients = new Map<string, any>()
export async function getServerSideSupabase(requestId?: string) {
const id = requestId || crypto.randomUUID()

if (!requestClients.has(id)) {
const client = await createServerClient()
requestClients.set(id, client)
}

return requestClients.get(id)
}
export function resetServerClient(requestId?: string) {
if (requestId) {
requestClients.delete(requestId)
} else {
requestClients.clear()
}
}

  • Framework: Next.js 14

  • Runtime: Node.js 22.x

  • Database: Supabase

  • Deployment: Vercel Production

Memory Usage Evidence:

  • Initial Memory: 20MB

  • Peak Memory: 2028 MB / 2048 MB (99% of limit)

  • Execution Duration: 30.43s / 30s (timeout)

  • Result: 504 Gateway Timeout