TypeScript

Fix Vite [commonjs--resolver] Failed to resolve entry

Reproduced on Vite 3, 4, 7 and 8: why a CommonJS dependency makes vite build stop on a package entry, why the odd "main" field is not the cause, and three fixes that each work on their own.

Mahdi Ben RhoumaMahdi Ben Rhouma
8 min read

TL;DR#

[commonjs--resolver] Failed to resolve entry for package means a CommonJS file in node_modules called require() on a package (or a package subfolder with its own package.json), Vite read that package.json, and the file it points to does not exist on disk. The path in the message tells you which copy is broken. In the Stack Overflow question it is a second copy of @mui/material nested inside another package. Fix it by removing or reinstalling the broken copy, by forcing Vite to use the root copy with resolve.dedupe, or with the alias from the answer posted under the question. The strange "main": "../node/{moduleName}/index.js" line is not the cause.

The message from the question:

text
[commonjs--resolver] Failed to resolve entry for package "/drone/src/node_modules/{module}/core/node_modules/@mui/material/LinearProgress". The package may have incorrect main/module/exports specified in its package.json: ENOENT: no such file or directory, lstat '/drone/src/node_modules/{module}/core/node_modules/@mui/material/LinearProgress/index.js'

The same failure, reproduced on Vite 4.5.14 with a minimal project (details below):

text
[commonjs--resolver] Failed to resolve entry for package "/app4.5.14/node_modules/@acme/core/node_modules/@mui/material/LinearProgress". The package may have incorrect main/module/exports specified in its package.json.
file: /app4.5.14/node_modules/@acme/core/index.js

Reading the message: three facts in one line#

[commonjs--resolver] is the plugin that asked, not the one that failed. Up to Vite 7, the production build runs on Rollup and Vite bundles @rollup/plugin-commonjs to turn require() calls into imports. That plugin registers a resolver hook named commonjs--resolver (source) and resolves each require() through the rest of the plugin chain with an isRequire: true flag (source). Vite's own resolver does the actual lookup and throws, and the error surfaces under the name of the hook that made the request. So the prefix tells you one thing: the import came from a CommonJS file, and the file: line names that file. A Failed to resolve raised while Vite loads its own config is a different path, covered in Failed to resolve "@tailwindcss/vite": ESM only, where the fix is the config's module type, not node_modules. And when the unresolved import is your own entry script rather than a dependency, see Rollup failed to resolve import "/src/main.tsx".

The quoted path is the folder Vite tried to treat as a package. In the question it ends in core/node_modules/@mui/material/LinearProgress: not the @mui/material at the project root, but a copy installed inside another dependency. That is the first thing to check.

Anything after the colon is the filesystem error Vite caught. Vite's packageEntryFailure appends the message of whatever exception it caught while resolving (Vite 4.5.14 source). The reproduction below ends with a plain full stop instead, so the ENOENT … lstat tail in the question comes from that machine's filesystem at the time, and the reproduction does not show why the call surfaced that way there. Both forms point at the same thing: the file named in the lstat was not where the package.json said it would be.

Why it happens: Vite reads "module" first, even for require()#

Each MUI v5 component folder ships a stub package.json. The one in the question is byte-for-byte the file published in @mui/material 5.0.0 for LinearProgress (unpkg):

json
{
  "sideEffects": false,
  "module": "./index.js",
  "main": "../node/LinearProgress/index.js",
  "types": "./index.d.ts"
}

../node/... looks like the obvious suspect, and it is the reason the asker linked the objectorarray issue. It is not what breaks. On Vite 4 the default list of main fields is module, then jsnext:main, then jsnext (constants.ts at v4.5.14, docs for Vite 4), and main is only a fallback when none of those is set. Vite applies that order when it resolves a folder that has a package.json, whether the request came from an import or a require(). For this stub it picks ./index.js. If that file is missing in the copy Vite is looking at, the build stops.

The reproduction proves it with a negative control. A CommonJS package @acme/core does require('@mui/material/LinearProgress') and carries its own nested copy of @mui/material 5.10.0, which Node's resolution (and Vite's) finds before the root copy. Deleting the file that main points to in that nested copy changes nothing:

text
$ rm node_modules/@acme/core/node_modules/@mui/material/node/LinearProgress/index.js
$ npx vite build
exit=0

Deleting the file that module points to produces the error:

text
$ rm node_modules/@acme/core/node_modules/@mui/material/LinearProgress/index.js
$ npx vite build
exit=1

So the question to ask is not "is my main field wrong" but "why is the file behind module missing from this copy". A duplicate copy on its own is harmless: with a complete nested copy the same project built normally. What the asker's CI did to that copy is not visible in the question; the reproduction simply removed one file to stand in for an incomplete install.

Fix#

Steps 2, 3 and 4 were each applied on their own to the broken tree, on Vite 4.5.14, and each one turned exit=1 into exit=0. Step 2 was tested by deleting the nested copy; the clean reinstall it recommends was not run against this hand-made tree.

1. Find every copy of the package#

List each installed copy and look inside the one named in the error:

bash
find node_modules -path "*/@mui/material/package.json" -not -path "*/@mui/material/*/*"
ls node_modules/@acme/core/node_modules/@mui/material/LinearProgress/

In the reproduction this shows two copies, and the nested folder has no index.js:

text
node_modules/@acme/core/node_modules/@mui/material/package.json
node_modules/@mui/material/package.json
text
LinearProgress.d.ts
LinearProgress.js
index.d.ts
linearProgressClasses.d.ts
linearProgressClasses.js
package.json

With a real npm install, npm ls @mui/material also prints the dependency path that leads to each copy.

2. Remove the broken copy#

If the copy is just damaged (an interrupted install, a restored CI cache, a node_modules copied from another machine), reinstall from scratch. (Copied folders cause their own failures too: Vite ERR_DLOPEN_FAILED on Windows shows identical files that still fail on a different machine.) The npm ci documentation states that an existing node_modules is removed before npm ci installs, so every package is extracted again:

bash
npm ci

In the reproduction the nested copy was made by hand, so the equivalent step was deleting it, after which the require falls through to the root copy:

bash
rm -rf node_modules/@acme/core/node_modules/@mui/material
npx vite build

If the nested copy exists because the parent package asks for a version range the root does not satisfy, aligning the versions (or upgrading the parent package) removes the duplicate for good.

3. Tell Vite to use the root copy: resolve.dedupe#

When the nested copy is required by a package you cannot change, Vite has an option for exactly this. The Vite documentation describes it:

If you have duplicated copies of the same dependency in your app (likely due to hoisting or linked packages in monorepos), use this option to force Vite to always resolve listed dependencies to the same copy (from project root).

js
import { defineConfig } from "vite";
 
export default defineConfig({
  resolve: {
    dedupe: ["@mui/material"],
  },
});

With only this change the reproduction built, and the bundle contained the real component code again (67.15 kB, the same size as the negative-control build above, where the nested copy still had its index.js), because @mui/material/LinearProgress now resolved to the complete root copy. In the question's config, add the resolve block next to the existing build and plugins keys.

4. Or alias the package to the root copy#

The answer on the question does the same thing by hand with resolve.alias. Copied into an ES module config, it built:

js
import path from "node:path";
import { defineConfig } from "vite";
 
export default defineConfig({
  resolve: {
    alias: [
      {
        find: /@mui\/material/,
        replacement: path.resolve(__dirname, "node_modules", "@mui", "material"),
      },
    ],
  },
});

The regular expression has no anchors, so it rewrites any import specifier that merely contains @mui/material. An anchored version that only matches the package itself and its subpaths, and that does not rely on __dirname, also built:

js
import path from "node:path";
import { fileURLToPath } from "node:url";
import { defineConfig } from "vite";
 
const root = path.dirname(fileURLToPath(import.meta.url));
 
export default defineConfig({
  resolve: {
    alias: [
      {
        find: /^@mui\/material(?=\/|$)/,
        replacement: path.resolve(root, "node_modules/@mui/material"),
      },
    ],
  },
});

Dedupe is shorter and says what you mean; the alias is useful when you need to point at a specific folder.

Verify the fix#

Run the production build and check both the exit status and the size of the chunk that should contain the component:

bash
npx vite build; echo "exit=$?"
text
dist/index.html                 0.14 kB │ gzip:  0.13 kB
dist/assets/index-3c5dd44d.js  67.15 kB │ gzip: 24.92 kB
✓ built in 4.60s

Do not stop at the exit code on Vite 5 or later; the next section shows why.

One email a month — the latest explained errors

What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.

The same broken tree on newer Vite versions#

The reproduction was repeated on Vite 3.1.8, 4.5.14, 7.3.7 and 8.3.4 (Node v22.23.3, Docker node:22-bookworm-slim, 13 October 2026). Two different shapes of the problem behave differently.

A subfolder stub with a missing entry (the MUI case in the question). Vite 3.1.8 and 4.5.14 fail as shown above; 3.1.8 names the bare specifier instead of the full path:

text
[commonjs--resolver] Failed to resolve entry for package "@mui/material/LinearProgress". The package may have incorrect main/module/exports specified in its package.json.

Vite 5 changed this path: PR #14626, "make directory package.json check best effort", first listed in the Vite 5.0.0-beta.12 changelog, skips the error when a folder's package.json resolves to nothing. On Vite 7.3.7 the same tree printed no error and no warning, exited 0, and produced a 0.95 kB bundle that still imports the module by name:

text
$ grep -o "import [a-z]* from\"@mui/material/LinearProgress\"" dist/assets/*.js
import l from"@mui/material/LinearProgress"

Nothing in dist/ provides that module, so the error has moved from the build to whatever later loads the bundle. A sudden drop in bundle size after an install is the visible symptom. Vite 8.3.4, which builds with Rolldown (a dependency of vite 8.3.4 instead of Rollup), also built the tree, but a marker added to each candidate file showed that it bundled the nested copy's main file (../node/LinearProgress/index.js), so the stub's main field matters there.

A package root whose main points to a missing file (the objectorarray issue the asker found, where main was dist/index.js but the file sat at the package root). Here PR #14626 does not apply, and Vite 7.3.7 still stops:

text
[commonjs--resolver] Failed to resolve entry for package "broken-entry". The package may have incorrect main/module/exports specified in its package.json.
file: /app/node_modules/@acme/legacy/index.js

Upgrading the offending package is the clean fix: objectorarray 1.0.5, published on 21 June 2021, has no main field at all (npm registry). Until you can upgrade, an alias to the file that does exist works, and built on Vite 7.3.7:

js
import path from "node:path";
import { fileURLToPath } from "node:url";
import { defineConfig } from "vite";
 
const root = path.dirname(fileURLToPath(import.meta.url));
 
export default defineConfig({
  resolve: {
    alias: {
      "broken-entry": path.resolve(root, "node_modules/broken-entry/index.js"),
    },
  },
});

Vite 8.3.4 built this case too, by falling back to the root index.js. Neither newer behaviour repairs the package, so treat a silent pass after an upgrade as a reason to check the bundle, not as a fix.

Whatever the version, the message is a pointer to a specific folder on disk. Open that folder, compare what its package.json promises with what is actually there, and fix the copy rather than the field that looks strange.

Further reading: Vite build Unexpected token in node_modules: fix define. Also see Failed to parse source map: fix the missing src warning.

Frequently Asked Questions

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote