How to Fix: “Module not found: Can’t resolve…” error in monorepo with sibling package installed while using Turbopack in DEV
Turbopack in Next.js 15 can fail to resolve a sibling workspace package in development even when the package is correctly installed, and the error usually appears as Module not found: Can’t resolve ‘…’. In a monorepo, this happens when the app, package manager workspace links, package export fields, and Turbopack’s dev-time module graph do not agree on how a sibling package should be resolved and transpiled.
Overview
The issue described in the reproduction branch is a classic monorepo + workspace package + Turbopack DEV mismatch. The application can see the sibling package at install time, but Turbopack does not always resolve it correctly during development if the sibling package is not exposed in a way Turbopack expects.
This is especially common when:
- A sibling package is linked through workspaces.
- The package uses TypeScript source files directly.
- The package relies on package.json exports or main/module fields that point to files Turbopack does not consume as expected.
- The Next.js app does not explicitly transpile workspace packages.
- The package lives outside the app directory and requires external directory access during local development.
If you want the short version: make the sibling package resolvable through package exports, make Next transpile it, and ensure the app is allowed to consume files from the workspace boundary.
Understanding the Root Cause
At a technical level, this bug is usually caused by the interaction of four systems:
- The package manager workspace linker creates a symlink or virtual link for the sibling package.
- Node-style package resolution reads
package.jsonfields likeexports,main, andtypes. - Next.js decides whether the package should be treated as application code that needs transpilation.
- Turbopack DEV builds its own module graph and may behave differently from webpack-based dev/build flows.
In many monorepos, sibling packages are authored like this:
// packages/ui/src/index.ts
export * from './components/button'
But the package metadata might still look like this:
{
"name": "@acme/ui",
"main": "index.js"
}
That creates a mismatch. The workspace dependency exists, but Turbopack cannot resolve a valid runtime entry because:
mainpoints to a file that does not exist.exportsis missing or incomplete.- The app imports TypeScript from outside its root, but Next is not configured to transpilePackages.
- The package is symlinked, but Turbopack treats it as an external dependency instead of local source.
Another common trigger is using subpath imports without matching exports:
import { Button } from '@acme/ui/button'
If the package only exports . and not ./button, Turbopack throws a module resolution error even if the source file physically exists.
Why it often appears only in DEV with Turbopack:
- Development resolution is stricter and more incremental.
- Turbopack may not mirror webpack behavior for certain workspace edge cases.
- Some packages work in production builds because they are prebuilt or resolved differently.
So the root problem is not just “the package is installed.” The real problem is: the sibling package is not exposed in a Turbopack-friendly way for monorepo development.
Step-by-Step Solution
The most reliable fix is to make the sibling package behave like a proper workspace library and tell Next.js to transpile it explicitly.
1. Verify the package is a real workspace dependency
In the app package, reference the sibling package through your workspace manager.
{
"dependencies": {
"@acme/shared": "workspace:*"
}
}
Then reinstall dependencies from the repository root.
pnpm install
# or
npm install
# or
yarn install
If the package is not linked correctly, nothing else will fix resolution in dev.
2. Fix the sibling package entry points
Open the sibling package’s package.json and define explicit exports.
{
"name": "@acme/shared",
"version": "1.0.0",
"private": true,
"type": "module",
"exports": {
".": {
"types": "./src/index.ts",
"default": "./src/index.ts"
}
}
}
If you import subpaths, export them too:
{
"exports": {
".": {
"types": "./src/index.ts",
"default": "./src/index.ts"
},
"./button": {
"types": "./src/button.tsx",
"default": "./src/button.tsx"
}
}
}
Do not leave stale main or module fields pointing to missing build artifacts unless those files actually exist.
3. Tell Next.js to transpile the sibling package
In the app’s next.config.js or next.config.ts, add transpilePackages.
/** @type {import('next').NextConfig} */
const nextConfig = {
transpilePackages: ['@acme/shared'],
}
module.exports = nextConfig
This is one of the most important fixes. It tells Next.js that the workspace package should be processed like application source rather than treated as a precompiled dependency.
4. Allow external monorepo directories if needed
If the workspace package is outside the app directory, enable external directory access.
/** @type {import('next').NextConfig} */
const nextConfig = {
transpilePackages: ['@acme/shared'],
experimental: {
externalDir: true,
},
}
module.exports = nextConfig
Depending on your Next.js version and repository layout, this can be necessary for local package source discovery.
5. Import from the package root first
Prefer this:
import { something } from '@acme/shared'
Instead of deep imports like:
import { something } from '@acme/shared/src/something'
Deep imports bypass the package contract and are more likely to break under Turbopack, especially in monorepos.
6. If the package is prebuilt, point exports to built output
If your sibling package is meant to compile before consumption, build it and point to the output directory.
{
"name": "@acme/shared",
"version": "1.0.0",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"scripts": {
"build": "tsup src/index.ts --format esm,cjs --dts"
}
}
Then run:
pnpm --filter @acme/shared build
This approach is often the safest if the package contains nontrivial transforms or unsupported source conventions.
7. Clean caches and restart dev
Turbopack can keep stale resolution state after package metadata changes.
rm -rf .next
rm -rf node_modules
pnpm install
pnpm dev
On Windows PowerShell:
Remove-Item .next -Recurse -Force
Remove-Item node_modules -Recurse -Force
pnpm install
pnpm dev
8. Confirm the package resolves from the app
Run a quick resolution test from the app directory.
node -e "console.log(require.resolve('@acme/shared'))"
If that fails, the problem is still in workspace linking or package exports rather than in the React or Next layer.
9. Example working configuration
App next.config.js:
/** @type {import('next').NextConfig} */
const nextConfig = {
transpilePackages: ['@acme/shared'],
experimental: {
externalDir: true,
},
}
module.exports = nextConfig
Sibling package package.json using source exports:
{
"name": "@acme/shared",
"private": true,
"type": "module",
"exports": {
".": {
"types": "./src/index.ts",
"default": "./src/index.ts"
}
}
}
App usage:
import { helper } from '@acme/shared'
This combination fixes the majority of Can’t resolve errors for sibling packages under Next.js 15 Turbopack DEV.
Common Edge Cases
1. Subpath exports are missing
If your app imports @acme/shared/foo, your package must export ./foo. Physical files are not enough when exports exists.
2. Mixed ESM and CommonJS
If the package uses type: module but your build output is CommonJS, or vice versa, resolution may fail or produce runtime import errors. Keep module format consistent.
3. TypeScript path aliases hide the real issue
If the app uses paths in tsconfig.json, the editor may resolve the package while Turbopack does not. TypeScript path mapping is not the same as runtime package resolution.
4. Package points to unbuilt dist files
This is one of the most common mistakes. If main or exports targets dist/index.js but dist does not exist in dev, Turbopack reports the package as unresolved.
5. Symlink behavior differs across package managers
pnpm, npm workspaces, and Yarn can expose workspace packages differently. A setup that works under one manager may need metadata cleanup under another.
6. CSS or asset imports from the sibling package
If the sibling package imports CSS, fonts, or SVGs, the package may resolve correctly while nested assets fail. In that case, inspect the sibling package source for unsupported asset boundaries.
7. Duplicate React installations
If the sibling package declares react as a dependency instead of a peer dependency, you may hit invalid hook call errors after fixing resolution. That is a separate monorepo packaging issue worth checking early.
8. Turbopack-specific dev bug
If everything is configured correctly and the issue still reproduces only with Turbopack DEV, you may be hitting a framework bug rather than a repo bug. In that case, a practical workaround is to temporarily use the non-Turbopack dev server until the upstream issue is fixed.
next dev
Instead of:
next dev --turbopack
That is not a true fix, but it can unblock development while preserving your monorepo structure.
FAQ
Why does the package work in TypeScript but fail in Next.js dev?
Because TypeScript type resolution and Turbopack runtime resolution are different systems. Your editor may understand a path through tsconfig, while Turbopack requires valid package exports and transpilation settings.
Do I always need transpilePackages for sibling workspace packages?
Not always, but in practice yes for most source-based monorepo packages. If the sibling package ships raw TypeScript, JSX, or modern syntax, adding transpilePackages is the recommended approach.
Should I export src files directly or build the package first?
Both can work. Exporting source files is convenient in a monorepo and often fine with transpilePackages. Prebuilding to dist is more explicit and usually more stable if the package is reused across multiple apps or tooling environments.
Final Takeaway
To fix Module not found: Can’t resolve… in a Next.js 15 monorepo using Turbopack in DEV, treat the sibling package as a real package, not just a nearby folder. Define correct exports, avoid broken main/module entries, enable transpilePackages, allow externalDir when needed, and restart with a clean cache. If the issue persists only under Turbopack, use the standard dev server as a temporary workaround while tracking the upstream framework behavior.
For reference, always validate your configuration against the reproduction repository linked in the issue and compare your package metadata, workspace layout, and import paths line by line.