The check added in 1ee4b5d did not do what its commit message claimed. It said
"refuses to start"; it did not. Throwing from `register()` does not stop the
server — the port is already bound by then, so Next logs "Failed to prepare
server" and the process STAYS ALIVE, answering 500 to every route.
That is worse than the problem it replaced. A TCP or HTTP healthcheck sees an
open port and reports the container healthy, so a dead deploy goes green and
keeps receiving traffic, and the operator sees 500s on /login AND
/favicon.ico AND everything else with no indication of why.
Measured, both before and after:
before port LISTENING, GET /login -> 500, GET /favicon.ico -> 500
after nothing listening, connection refused, EXIT_CODE=1
A non-zero exit is what a deployment platform reads as a failed deploy. The
numbered problem list goes to stderr before exiting, so every restart attempt
reprints the reason.
The Node-only half moves to src/instrumentation-node.ts, reached by dynamic
import. instrumentation.ts is compiled for EVERY runtime the app uses, and
src/proxy.ts makes this app compile an Edge one, where process.exit does not
exist — Turbopack flagged it ("A Node.js API is used (process.exit) which is
not supported in the Edge Runtime") even though the call sits behind a
NEXT_RUNTIME guard that can never let it run there. A runtime guard is not a
bundling boundary; a separate module behind a dynamic import is. The build is
warning-free again.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
46 lines
2.5 KiB
TypeScript
46 lines
2.5 KiB
TypeScript
/**
|
|
* Boot-time configuration check.
|
|
*
|
|
* ── The problem this exists to end ───────────────────────────────────────
|
|
* Every required variable in this app is read LAZILY, on the first request
|
|
* that needs it. That is deliberate and must stay that way: `next build`
|
|
* collects page data with NODE_ENV=production and none of these variables
|
|
* present, so reading them at module scope fails the BUILD instead of the
|
|
* deployment (it did, once — "Failed to collect page data for /api/assistant",
|
|
* see apiClient.ts).
|
|
*
|
|
* The cost of lazy reads is that a misconfigured container LOOKS healthy. It
|
|
* starts, it serves the sign-in page, and the fault only appears when somebody
|
|
* tries to use it — as a 500 on a form, with the real reason buried in a
|
|
* response body. Three separate misconfigurations were diagnosed that way, one
|
|
* round trip at a time.
|
|
*
|
|
* This closes the gap without touching the lazy reads. `register()` runs once
|
|
* when a server instance starts and NEVER during a build — Next itself returns
|
|
* early when NEXT_PHASE is 'phase-production-build' (see
|
|
* server/lib/router-utils/instrumentation-globals.external.js). So the checks
|
|
* below run in exactly the situation they are about: a real server, booting,
|
|
* with a real environment.
|
|
*
|
|
* ── Why it throws ────────────────────────────────────────────────────────
|
|
* A container missing either variable cannot serve a single authenticated
|
|
* request. Refusing to start turns that into a failed deploy with a named
|
|
* cause in the log pane, which Dokploy surfaces immediately, instead of a
|
|
* green healthcheck in front of a console nobody can sign into. It also stops
|
|
* a broken image from replacing a working one.
|
|
*/
|
|
export async function register() {
|
|
/**
|
|
* Node only. `register()` is invoked once per runtime, and src/proxy.ts makes
|
|
* this app compile an Edge one too — without this guard the same check would
|
|
* run and log twice on every boot. The Node server is the process that serves
|
|
* every route handler, so validating there is what matters.
|
|
*/
|
|
if (process.env.NEXT_RUNTIME !== 'nodejs') return;
|
|
|
|
// Dynamic, so the Edge bundle never pulls in the Node-only module. See that
|
|
// file for why a runtime guard alone was not enough.
|
|
const {checkConfiguration} = await import('./instrumentation-node');
|
|
await checkConfiguration();
|
|
}
|