Render: Vite build fails with "Rollup failed to resolve import "@sentry/capacitor"" on prod, but not stage
Render build "passes" on the stage service and fails on prod for the same commit: [vite]: Rollup failed to resolve import "@sentry/capacitor" from src/hooks.client.ts, and a clear-cache retry fails differently with sh: 1: vite: not found.
The package is a plain package.json dependencies entry, present in package-lock.json, and CI (node 22) is green. Nothing in the vite/rollup config explains it.
The real story is ~250 lines above the error, at the head of the build log:
==> Downloading cache...
==> Requesting Node.js version >=20 <21
==> Using Node.js version 20.20.2 via environment variable NODE_VERSION
==> Running build command './scripts/render_fruit_build.sh'...
npm error code EBADENGINE
npm error engine Not compatible with your version of node/npm: @capacitor/cli@8.5.0
npm error notsup Required: {"node":">=22.0.0"}
npm error notsup Actual: {"npm":"10.8.2","node":"v20.20.2"}npm had been exiting 1 on every build of BOTH services for two days.
Three defects stacked, and the platform hid all three.
.npmrchadengine-strict=true, so an engines mismatch anywhere in the tree is a hard error rather than a warning. A lockfile refresh had pulled@capacitor/cli@8.5.0(engines.node >=22) in as a devDependency.- Render's
NODE_VERSIONenv var (still the old">=20 <21") overrides BOTH.node-versionandpackage.jsonengines, so every build ran node 20 regardless of the repo. Wideningengineschanges nothing while that env var exists. - The build script had no
set -e. npm exited 1, the script continued, andvite buildran against whatevernode_modulesRender restored from THAT SERVICE's build cache. Render's build cache is per service: stage's tarball happened to contain the newly added dependency, prod's (3 days older) did not. That is the entire "green on stage, red on prod" mystery. Neither service had installed the lockfile for days; only their caches differed. The clear-cache retry then had no node_modules at all, hencevite: not found.
Fixes, in order of leverage:
set -o errexit -o nounset -o pipefailat the top of every hosted build script. A build step that can fail silently eventually ships a build made of stale bytes.npm ci, nevernpm install, in hosted builds: it wipes node_modules and installs exactly the lockfile, so no build can inherit a stale dependency tree from a platform cache. npm's own~/.npmdownload cache keeps it fast.- Delete platform runtime-version env vars (
NODE_VERSION) and let the repo own the pin via.node-version+engines; otherwise every runtime bump is invisible per-service config drift. - Echo
node -v/npm -vas the first line of the build. - If you have a promote-stage-to-prod command, make it refuse when the two services differ in rootDir / build command / start command / toolchain env vars, and make it WAIT for each triggered deploy to reach a terminal status instead of fire-and-forget. A green stage build only means something if prod builds the same commit the same way.
Corollary: check the workaround commits filed in the days before. Two commits had "fixed" this same rollup resolve error by adding the unresolvable package to build.rollupOptions.external. That silences the error but emits a bare await import("@pkg/name") into the CLIENT bundle, which a Capacitor WebView cannot resolve (no import maps at file://), so it silently broke native Google sign-in in shipped APKs. Symptom-level fixes to this failure mode are worse than the failure.