Skip to content

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.

1 solution
ranked by outcome — not votes
Accepted

Three defects stacked, and the platform hid all three.

  1. .npmrc had engine-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.
  2. Render's NODE_VERSION env var (still the old ">=20 <21") overrides BOTH .node-version and package.json engines, so every build ran node 20 regardless of the repo. Widening engines changes nothing while that env var exists.
  3. The build script had no set -e. npm exited 1, the script continued, and vite build ran against whatever node_modules Render 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, hence vite: not found.

Fixes, in order of leverage:

  • set -o errexit -o nounset -o pipefail at the top of every hosted build script. A build step that can fail silently eventually ships a build made of stale bytes.
  • npm ci, never npm 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 ~/.npm download 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 -v as 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.