How to Fix: Turbopack does not handle CommonJS modules properly when “type”: “commonjs” is specified in package.json
Turbopack breaks CommonJS resolution when "type": "commonjs" is set in package.json: how to fix it in Next.js
If your Next.js app runs correctly with the classic dev server but fails under Turbopack after setting "type": "commonjs" in package.json, the problem is usually not your app code. The failure comes from how module format detection, ESM/CommonJS interop, and Turbopack’s package analysis interact when a dependency like Jotai ships modern exports.
The issue reproduced in this example repository happens because Turbopack does not always handle packages the same way as Webpack when the project is explicitly marked as CommonJS. In practice, this can cause import analysis to select the wrong entrypoint, misread package exports, or fail when mixing require() semantics with dependencies published primarily for ES modules.
Understanding the Root Cause
Setting "type": "commonjs" in the root package.json tells Node.js that local .js files should be treated as CommonJS by default. That affects how your project files are interpreted, but modern frontend packages often expose multiple builds through fields like main, module, and especially exports.
Here is where the mismatch happens:
- Next.js application code may still use
import/exportsyntax because Next transforms it. - A package like Jotai may expose ESM-oriented entrypoints through conditional exports.
- Turbopack tries to resolve those conditions very aggressively for speed.
- When the app itself is marked as CommonJS, Turbopack can incorrectly infer or apply the wrong module interpretation path.
In other words, the bug is not simply that CommonJS is unsupported. The real problem is that Turbopack’s module resolution and interop logic can become inconsistent when the application package type is forced to CommonJS while dependencies rely on modern export maps.
This is why the same app may behave differently across these scenarios:
- Works with Webpack-based Next dev mode
- Fails with Turbopack
- Works again after removing
"type": "commonjs" - Works if imports are rewritten or if package boundaries change
Technically, Turbopack is expected to support mixed ecosystems, but this issue shows a gap in its current handling of CommonJS package scope plus dependency export resolution.
Step-by-Step Solution
The most reliable fix today is to avoid declaring the whole Next.js app as CommonJS unless you truly need it. Next.js and most modern React dependencies work best when the app package remains neutral or ESM-friendly.
1. Remove "type": "commonjs" from the root package.json
Open package.json and remove the field entirely.
{
"name": "my-next-app",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start"
},
"dependencies": {
"jotai": "^2.0.0",
"next": "latest",
"react": "latest",
"react-dom": "latest"
}
}
This allows Next.js and Turbopack to resolve your app and its dependencies using the default behavior expected by the ecosystem.
2. If you need CommonJS for a specific config file, rename only that file
Do not convert the entire project to CommonJS just to support one config script. Instead, use file-level module extensions:
- Use
.cjsfor CommonJS files - Use
.mjsfor ESM files where needed
Example:
next.config.cjs
postcss.config.cjs
some-script.cjs
Example CommonJS config:
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
};
module.exports = nextConfig;
This isolates CommonJS to the files that actually need it without forcing Turbopack to treat the whole package through a CommonJS lens.
3. Reinstall dependencies and clear the cache
After changing package module settings, clear generated artifacts so resolution state is rebuilt cleanly.
rm -rf .next node_modules
pnpm install
pnpm dev
If you use npm:
rm -rf .next node_modules package-lock.json
npm install
npm run dev
4. Keep import syntax standard in app code
Prefer ES module imports inside Next.js components and pages.
import { atom, useAtom } from 'jotai';
const countAtom = atom(0);
export default function Counter() {
const [count, setCount] = useAtom(countAtom);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
Avoid mixing patterns like this in application code unless absolutely necessary:
const jotai = require('jotai');
Next.js supports ESM-style authoring very well, and this reduces ambiguity for Turbopack.
5. Use Webpack temporarily if removing CommonJS is impossible
If your project architecture requires root-level CommonJS and you cannot change it immediately, the practical workaround is to run Next.js without Turbopack until the bug is fixed upstream.
next dev
Or in package.json:
{
"scripts": {
"dev": "next dev"
}
}
This is not the ideal long-term solution, but it is often the fastest way to unblock development.
6. Track the upstream fix
Because this is a Turbopack behavior issue, check the relevant project updates in the Next.js issue tracker and test again after upgrading Next.js.
pnpm up next
Then retry:
pnpm dev
Recommended final setup
For most apps, this structure avoids the bug and stays compatible with modern packages:
package.json // no "type": "commonjs"
next.config.cjs // CommonJS only if needed
app/page.tsx // ESM imports
components/*.tsx // ESM imports
Common Edge Cases
1. A third-party dependency ships broken export maps
Even after removing "type": "commonjs", some libraries publish inconsistent exports definitions. Turbopack may fail on packages that expose different files for import and require but do not declare them correctly.
What to do: test the package with plain next dev, check the library’s package metadata, and pin to a known working version if necessary.
2. Config files stop working after removing CommonJS
If you were relying on root-level CommonJS, files using module.exports may break once the package type changes.
What to do: rename those files to .cjs instead of reintroducing "type": "commonjs" globally.
3. Mixed monorepo behavior
In a monorepo, one package may declare "type": "commonjs" while another is ESM. Turbopack can behave differently depending on where the dependency boundary sits.
What to do: keep the Next.js app package itself free of root-level CommonJS unless required, and isolate legacy packages behind explicit build outputs.
4. Dynamic require calls
Code such as require(someVariable) is much harder for bundlers to analyze than static imports.
What to do: replace dynamic CommonJS loading with static imports wherever possible.
5. Server-only utilities leaking into client components
A CommonJS utility might work on the server but fail once imported into a client boundary, especially if the package expects Node-only APIs.
What to do: verify whether the failing file is inside a 'use client' tree and move Node-specific logic back to server code.
FAQ
Does Next.js support CommonJS at all?
Yes, but support is contextual. Config files, some server scripts, and dependency interop can still use CommonJS. The issue here is that declaring the entire app package as "type": "commonjs" can confuse Turbopack’s resolution pipeline.
Why does it work with Webpack but not with Turbopack?
Webpack and Turbopack do not share identical module resolution internals. Turbopack is newer and optimized differently, so edge cases around export conditions and CommonJS interop can appear in Turbopack first.
Should I switch my entire app to ESM?
For most modern Next.js apps, yes, or at least avoid forcing CommonJS at the package root. You do not have to rewrite every script immediately; using .cjs for legacy files is usually enough.
The practical takeaway is simple: do not set "type": "commonjs" in the root of a Turbopack-powered Next.js app unless you have no alternative. Keep the app ESM-friendly, isolate legacy CommonJS files with .cjs, and fall back to Webpack only if you need a temporary workaround.