fix(server): resolve socket.io deps via NODE_PATH + polyfill AsyncLocalStorage
All checks were successful
Build & Push Docker Images / lint (push) Successful in 2m43s
Build & Push Docker Images / build-and-push (push) Successful in 7m31s

Two runtime defects in the crm-app prod image (never exercised before this
deploy; CI only builds + pushes):

1. Replacing the standalone node_modules wholesale to add socket.io's deps
   swapped out Next's standalone-tuned `next` and broke its runtime
   ("Invariant: AsyncLocalStorage accessed in runtime where it is not
   available"). Instead, stage the complete hoisted prod tree in a separate
   dir on NODE_PATH: the standalone node_modules (and its `next`) stay
   intact, and only the socket server's otherwise-missing deps
   (engine.io→accepts/ws/cors, @socket.io/redis-adapter) fall through to it.

2. Defensively set globalThis.AsyncLocalStorage before Next's app-render
   modules load, via a preamble that is the first import in server.ts.
   Next's node-environment-baseline normally sets it during the standalone
   bootstrap, but the custom server can load app-render storage first.
   Verified in the esbuild bundle that the assignment runs before
   require("next").

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-02 23:34:28 +02:00
parent 2315b58764
commit 319fd7fd1a
3 changed files with 30 additions and 5 deletions

View File

@@ -45,11 +45,17 @@ COPY --from=builder --chown=nextjs:nodejs /app/dist/server.js ./server-custom.js
# --packages=external) resolves socket.io and its FULL transitive closure
# (engine.io → accepts/ws/cors, @socket.io/redis-adapter, ...) from
# node_modules at runtime. The Next tracer omits these from
# .next/standalone because no Next route imports the socket server, and
# cherry-copying just socket.io/ leaves its deps unresolved
# (MODULE_NOT_FOUND 'accepts'). Overlay the complete prod dependency tree
# (flat/hoisted layout → symlink-safe copy) on top of the traced subset.
COPY --from=prod-deps --chown=nextjs:nodejs /app/node_modules ./node_modules
# .next/standalone because no Next route imports the socket server
# (→ MODULE_NOT_FOUND 'accepts'). Stage the complete hoisted prod tree in
# a SEPARATE dir on NODE_PATH rather than touching the standalone
# node_modules: overlaying real dirs onto its pnpm symlinks (e.g.
# @react-pdf/renderer) fails the COPY, and replacing it wholesale swaps
# out the standalone-tuned `next` and breaks Next's runtime
# (AsyncLocalStorage invariant). NODE_PATH is searched AFTER the local
# walk, so Next still resolves its own deps from ./node_modules; only the
# socket server's otherwise-missing deps fall through to here.
COPY --from=prod-deps --chown=nextjs:nodejs /app/node_modules ./_server_deps
ENV NODE_PATH=/app/_server_deps
USER nextjs
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \