Debugging Turbopack Module Resolution Errors in Next.js
You switched dev to next dev --turbopack and a build that worked under Webpack now throws Module not found: Can't resolve '@/lib/utils' or Can't resolve 'some-pkg' — this guide diagnoses why Turbopack’s resolver disagrees with Webpack and how to fix each class of failure. Resolution is one node type in the demand-driven graph described in Turbopack Incremental Compilation; when its inputs are wrong, every downstream transform fails, so fixing resolution first is non-negotiable.
Turbopack ships in Next.js and is enabled with next dev --turbopack (stable as of Next.js 15). Its resolver is a fresh Rust implementation, not a port of Webpack’s enhanced-resolve, so configuration that Webpack tolerated implicitly must be made explicit.
How Turbopack Resolution Differs from Webpack
Three differences cause most regressions when teams flip the --turbopack flag:
- No implicit extension guessing beyond the configured list. Webpack’s defaults plus loose project config often masked an import missing its
.ts/.tsx. Turbopack resolves only the extensions inresolveExtensions(defaults:.tsx .ts .jsx .js .mjs .cjs .json), in order. - Stricter package
exports/conditions handling. Turbopack honors theexportsmap and condition keys (import,require,node,default) precisely. A package that “worked” in Webpack because it fell back tomainmay now resolve to nothing if itsexportsmap omits the subpath you import. - Aliases are not inherited from a Webpack config. Any
resolve.alias,webpack()mutation, ortsconfig-paths-webpack-pluginyou relied on is invisible to Turbopack. You restate aliases underturbopack.resolveAlias, and tsconfigpathsare read directly.
Prerequisites & Reproducible Setup
# Next.js 15.3.x / Node 20+
npx create-next-app@latest tp-resolve-demo --ts --app --no-tailwind
cd tp-resolve-demo
npm pkg set scripts.dev="next dev --turbopack"
mkdir -p src/lib && echo "export const ping = () => 'pong';" > src/lib/utils.ts
Now import it from a page with import { ping } from '@/lib/utils'. If the @/* path is not configured for Turbopack, npm run dev throws Module not found: Can't resolve '@/lib/utils'.
Diagnosis Workflow
- Read the full error. Turbopack prints the failing specifier and the importing file. A
@/-prefixed specifier points at an alias/paths problem; a bare name points atexports/install; a relative path points at extensions or a typo. - Check tsconfig
pathsare mirrored. Turbopack readscompilerOptions.pathsfromtsconfig.json, but a custombaseUrlor a non-standard config location can break this; confirm the mapping resolves to a real file on disk. - List what the package actually exports. For a bare-import failure run
cat node_modules/<pkg>/package.json | grep -A20 '"exports"'and confirm the subpath you import is declared. - Trace the resolver. Set
TURBOPACK=1for verbose internals and capture the attempted candidate paths:
# Verbose Turbopack internals (Next.js 15.3.x)
TURBOPACK=1 NEXT_TURBOPACK_TRACING=1 next dev --turbopack 2>&1 | tee resolve-trace.log
grep -i "resolve" resolve-trace.log
- Check for symlinks in a monorepo:
ls -l node_modules/<pkg>— a symlink into a workspace package that is not declared as a dependency will not be tracked.
Solution Configuration
This next.config.js and matching tsconfig.json make every resolution input explicit. Both are complete and runnable.
// next.config.js — Next.js 15.3.x / Node 20+
/** @type {import('next').NextConfig} */
const nextConfig = {
turbopack: {
// Restate aliases — Turbopack does NOT read a Webpack resolve.alias.
// Keys are exact specifiers or end in /* to mirror tsconfig paths.
resolveAlias: {
'@/*': ['./src/*'],
// Force a single React copy across symlinked monorepo packages.
react: './node_modules/react',
'react-dom': './node_modules/react-dom',
},
// Extensions are tried in this order. Add custom ones you import without an extension.
resolveExtensions: ['.tsx', '.ts', '.jsx', '.js', '.mjs', '.cjs', '.json'],
// Pick condition keys for packages with an exports map (browser-first here).
resolveConditions: ['browser', 'import', 'require', 'default'],
},
};
module.exports = nextConfig;
// tsconfig.json — read directly by Turbopack for paths
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
"moduleResolution": "bundler"
}
}
When tsconfig.json paths and turbopack.resolveAlias both define @/*, keep them identical. Drift between the two is a common cause of an editor that resolves the import while the dev server does not.
Verification
After saving the config, restart the dev server — Turbopack does not hot-reload next.config.js, so a stale process keeps throwing the old error. A clean start prints the route compiling without a Module not found line, and the page renders the imported value:
rm -rf .next && next dev --turbopack
# expect: "✓ Compiled / in <N>ms" and no "Module not found" in output
For a bare-package failure, confirm resolution lands on a real file by re-running the trace and grepping for the resolved absolute path:
TURBOPACK=1 next dev --turbopack 2>&1 | grep -i "<pkg-name>"
# expect a node_modules/<pkg>/dist/... path, not a "failed to resolve" line
Gotchas & Edge Cases
exports map omits the subpath. import x from 'pkg/internal' fails when pkg’s exports does not declare ./internal. You cannot force it from Turbopack config — either import a declared entry point or patch the package’s exports via your package manager’s override/patch mechanism.
Wrong condition resolved. A package that ships both ESM and CJS can resolve to the CJS branch if your resolveConditions order is wrong, surfacing as a runtime is not a function rather than a resolution error. Put import before require for ESM-first packages.
Monorepo symlink not tracked. A workspace package symlinked into node_modules but absent from the importing package’s dependencies/devDependencies will not be resolved or watched. Declare it as a workspace dependency. These same symlink gaps cause stale rebuilds, covered in Turbopack Incremental Compilation.
Case-sensitivity. Importing @/Lib/utils resolves on macOS and fails in Linux CI. Turbopack is case-sensitive on case-sensitive filesystems; match the on-disk casing exactly.
Related
- Turbopack Incremental Compilation — the resolve node within the demand-driven graph and its watcher behavior.
- Configuring Turbopack cache for Next.js projects — how alias and resolution inputs feed cache keys.
- esbuild & Turbopack Workflows — resolution behavior across the wider engine comparison.