Skip to content
Latchkey

Vite build "[rollup] Could not resolve import" (external) in CI

During vite build, Rollup could not resolve an import. Either the package is missing, the path is wrong, or it is a Node built-in that should be marked external rather than bundled.

What this error means

A vite build aborts with [rollup] Could not resolve "x" from .... The dev server worked because Vite resolves differently from the production Rollup bundle.

node
[vite]: Rollup failed to resolve import "fs" from "src/loader.ts".
This is most likely unintended because it can break your application
at runtime.

Diagnose it: the shell in CI is not your shell

Package scripts run under a different shell, a different PATH, and a non-interactive environment on a runner. Most scripts that fail only in CI are relying on something the login shell gave them locally: a tool on PATH, an environment variable from a dotfile, or a TTY.

Terminal
# what the script can actually see
npm run env | grep -E "^(PATH|NODE_ENV|CI)=" 

# is the binary on PATH for the script, not just for you?
npm exec -- which <tool> || echo "not resolvable from npm scripts"

# run the exact script with tracing
sh -x -c "$(node -p "require('./package.json').scripts.build")"

Common causes

A missing dependency or wrong path

The imported package is not installed, or the relative path does not exist in the build context. The dev server tolerated it; the build does not.

A Node built-in being bundled for the browser

A fs/path-style import pulled into a browser bundle has no resolution. It must be externalized or removed for client builds.

How to fix it

Install the dependency or fix the path

Add the missing package, or correct the import path so Rollup can resolve it.

  1. Confirm the package is in package.json and the lockfile.
  2. Check the relative path exists in the build context.
  3. Run npm ci && vite build on a clean checkout to verify.

Mark legitimate externals

For Node built-ins or deps that should not be bundled, declare them external in the Rollup options.

vite.config.ts
// vite.config.ts
export default {
  build: {
    rollupOptions: { external: ['fs', 'path'] },
  },
};

Make failures fail the job

A multi-command script can report success while a middle command failed, which produces the worst kind of CI result: a green build that shipped something broken.

.github/workflows/ci.yml
# pipefail is NOT set by default in every runner shell
- name: Build
  shell: bash
  run: |
    set -euo pipefail
    npm run build | tee build.log

How to prevent it

  • Run the production build (not just dev) in CI to catch resolution gaps.
  • Keep dependencies declared in package.json, not relied on transitively.
  • Keep Node-only code out of browser bundles.

Frequently asked questions

What causes Vite build "[rollup] could not resolve import" (external) in CI?
There are 2 common causes: a missing dependency or wrong path and a node built-in being bundled for the browser. The imported package is not installed, or the relative path does not exist in the build context.
How do I fix Vite build "[rollup] could not resolve import" (external) in CI?
There are 2 fixes depending on which cause you have: install the dependency or fix the path and mark legitimate externals. Work through them in order, since the first is the most common.
What does Vite build "[rollup] could not resolve import" (external) in CI actually mean?
A vite build aborts with [rollup] Could not resolve "x" from ....
How do I stop Vite build "[rollup] could not resolve import" (external) in CI happening again?
Run the production build (not just dev) in CI to catch resolution gaps. The prevention section lists 3 changes that keep it from recurring.

Related guides

References

Not every red build is your code. Latchkey repairs the ones that are not, on the runner. Start free → 30-day trial · No credit card