From b6f6701edac0b435d167686e68894234ac81a9f3 Mon Sep 17 00:00:00 2001 From: KimP0ssible-no Date: Wed, 15 Apr 2026 12:49:48 +0200 Subject: [PATCH] fix: cap V8 heap and enable effective garbage collection for nextjs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add --max-old-space-size=1024 to the node process running the Next.js server. This caps V8's old-generation heap at 1024MB, forcing the garbage collector to run more aggressively as memory usage approaches the limit. Previously, users saw Next.js RSS grow to 4GB+. This was caused by a reference chain that prevented GC from freeing memory: AfterContext (vercel/next.js#89091) retained ServerResponse objects after client disconnect, which held zlib Transform streams (from compress: true), which allocated native C++ buffers outside V8's heap entirely — invisible to GC and uncapped by --max-old-space-size. Combined with the prior commit's fixes (compress: false moves gzip to nginx, httpBatchLink closes connections faster, socket-cleanup.cjs destroys zombie CLOSE_WAIT sockets every 60s), GC can now actually reclaim memory because: - zlib native buffers no longer exist (compress: false) - The AfterContext → ServerResponse reference chain is broken by socket cleanup destroying the underlying sockets - The JS heap is capped at 1024MB so V8 GCs more frequently for the memory it does control Also fixes the Dockerfile to copy socket-cleanup.cjs from the builder stage instead of the host context. Ref: https://github.com/homarr-labs/homarr/issues/3759 --- Dockerfile | 2 +- apps/nextjs/socket-cleanup.cjs | 1 - scripts/run.sh | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index f019892a8..936442cc6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -52,7 +52,7 @@ COPY --from=builder /app/packages/db/migrations ./db/migrations # Automatically leverage output traces to reduce image size # https://nextjs.org/docs/advanced-features/output-file-tracing COPY --from=builder /app/apps/nextjs/.next/standalone ./ -COPY apps/nextjs/socket-cleanup.cjs ./apps/nextjs/socket-cleanup.cjs +COPY --from=builder /app/apps/nextjs/socket-cleanup.cjs ./apps/nextjs/socket-cleanup.cjs COPY --from=builder /app/apps/nextjs/.next/static ./apps/nextjs/.next/static COPY --from=builder /app/apps/nextjs/public ./apps/nextjs/public COPY scripts/run.sh ./run.sh diff --git a/apps/nextjs/socket-cleanup.cjs b/apps/nextjs/socket-cleanup.cjs index 7d1310ce9..3847cf0c4 100644 --- a/apps/nextjs/socket-cleanup.cjs +++ b/apps/nextjs/socket-cleanup.cjs @@ -3,7 +3,6 @@ // after client disconnect, leaving sockets in CLOSE-WAIT indefinitely. // // Usage: node --require ./socket-cleanup.cjs server.js -// or: NODE_OPTIONS="--require /app/socket-cleanup.cjs" node server.js const INTERVAL_MS = 60_000; // Check every 60 seconds diff --git a/scripts/run.sh b/scripts/run.sh index 6aae28fcf..3b706f619 100644 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -51,7 +51,7 @@ TASKS_PID=$! node apps/websocket/wssServer.cjs & WSS_PID=$! -node --require apps/nextjs/socket-cleanup.cjs apps/nextjs/server.js & +node --max-old-space-size=1024 --require apps/nextjs/socket-cleanup.cjs apps/nextjs/server.js & NEXTJS_PID=$! # Function to handle SIGTERM and shut down services