Monorepo Module Resolution#
Strapi's pnpm monorepo has two distinct (though related) classes of module-resolution problem under strict/isolated node_modules layouts (pnpm node-linker=isolated / enableGlobalVirtualStore, Yarn PnP, npm --install-strategy=nested):
- Context-constrained
require.resolve()inside the bundler — picking up a wrong version of a shared singleton at build time. - Missing dependency declarations — packages that work under hoisted layouts but fail at runtime or type-check time when hoisting is absent.
1. Context-Constrained require.resolve() and Bundler Aliases#
The problem#
pnpm installs each package into its own scoped directory and uses symlinks rather than hoisting everything to a flat node_modules. A dynamically-loaded plugin chunk can therefore only see modules that are direct dependencies of that plugin, not the admin's pinned versions — unless those versions are forced via bundler aliases.
getModulePathFrom(hostPackage, mod) in packages/core/strapi/src/node/core/resolve-module.ts is the central fix. It calls resolveFrom (the resolve-from library) scoped to a specific host package directory, then walks up with readPkgUp to return the package root. The exported convenience wrapper getModulePath(mod) hard-codes @strapi/admin as the host, ensuring all Vite/Webpack aliases resolve from @strapi/admin's closure rather than from @strapi/strapi's location.
Why the host matters#
A bare require.resolve(mod) resolves starting from the calling module's directory (@strapi/strapi). In a pnpm workspace that also contains a package depending on, say, @reduxjs/toolkit@^2, pnpm hoists that version to .pnpm/node_modules/. A bare resolve from @strapi/strapi's location finds the hoisted major before @strapi/admin's pinned 1.9.x. This caused a concrete regression in 5.48.1: the entire admin Vite build was force-aliased to RTK 2.x, causing "Duplicate middleware references found when creating the store" and a blank admin panel . The fix — PR #26756 — replaced the bare call with resolveFrom scoped to @strapi/admin.
What is aliased#
The canonical list of packages that receive resolve.alias, resolve.dedupe, and optimizeDeps.include treatment is in admin-vite-alias-modules.ts: React core, Redux stack, DnD, @strapi/design-system, lodash, invariant, prismjs . CodeMirror packages use a separate list in admin-vite-singleton-modules.ts, resolved from @strapi/design-system's closure. See the Vite Build Configuration KB article for the full three-layer deduplication system.
2. Missing Dependency Declarations#
How hoisting conceals missing deps#
With Yarn's default node-modules linker, everything is hoisted to a single root node_modules, so a package can require anything in the tree regardless of whether it declared it. Switch to a strict linker and every undeclared dependency fails immediately.
Monorepo build deps (devDependencies)#
PR #27407 and PR #27249 swept the monorepo to declare tooling and per-workspace deps that were previously resolved by hoisting: tsconfig, eslint-config-custom, typescript, jest, @types/*, and workspace cross-deps like @strapi/admin, @strapi/strapi, and @strapi/database across ~30 packages .
Plugin runtime deps — @strapi/admin missing in plugins#
PR #27337 (merged Aug 2026) fixed three plugins that import @strapi/admin/strapi-admin at runtime but never declared it:
@strapi/plugin-users-permissions— real runtime imports inadmin/src/pages/Providersandadmin/src/pages/EmailTemplates@strapi/plugin-graphqland@strapi/plugin-sentry— type-only imports that end up in published.d.tsfiles
Under an isolated layout, Vite's pre-bundling fails with:
✘ [ERROR] Could not resolve "@strapi/admin/strapi-admin"
node_modules/@strapi/plugin-users-permissions/dist/admin/pages/Providers/index.mjs:3
The fix adds @strapi/admin as both a peer dependency (to guarantee one shared instance, avoiding React context duplication) and a devDependency (for builds and type checking) .
esbuild peer not declared#
PR #27481 (open) fixed @strapi/strapi and @strapi/upgrade, which both use esbuild-register to load user TypeScript config files. esbuild-register declares esbuild as a peer and does not bundle it. Neither package declared esbuild, so any strict install that doesn't happen to pull in esbuild-loader (or tsx in dev) fails at runtime with Cannot find module 'esbuild' .
Published type declarations (@types/* and leaked inference)#
Issue #27744 and PR #27743 (open, Sept 2026) address the most pervasive category: published .d.ts files across 12 core packages reference modules their package.json never declares. Three sub-categories :
| Category | Examples | Effect |
|---|---|---|
@types/* only in devDependencies | @types/react, @types/koa, @types/lodash | Prop types silently degrade to any |
| Inferred runtime types not declared | redux, redux-thunk, @remix-run/router, logform, axios | TS2307: Cannot find module |
@strapi/* cross-refs reached via peer | @strapi/types via @strapi/admin peer | Two TypeScript module identities if peer virtualizes |
Under skipLibCheck: true (the default in Strapi's generated tsconfig.json) these errors are hidden inside .d.ts files and propagate silently as any into consumer code. Under skipLibCheck: false with a strict layout, 139 errors originate inside Strapi's own packages .
The fix pattern :
@types/*→ optional peer dependencies (deduplicates against consumer's copy;optional: trueavoids unmet-peer warnings for JS consumers)- Real runtime modules → plain
dependencies, pinned to already-resolved versions @strapi/*cross-references → optional peer dependencies (prevents a second package copy from splitting TypeScript module identity)
Key Files#
| File | Role |
|---|---|
resolve-module.ts | getModulePathFrom() / getModulePath() — scoped module resolution |
admin-vite-alias-modules.ts | Canonical alias/dedupe package lists |
vite/config.ts | Applies aliases via buildAdminViteResolveAliases() |
Related PRs / Issues#
| Reference | Summary |
|---|---|
| PR #26756 | Scope getModulePath to @strapi/admin closure (RTK version mismatch fix) |
| PR #27249 | Declare per-workspace deps resolved by hoisting |
| PR #27337 | Add missing @strapi/admin peer+dev dep to graphql/sentry/users-permissions plugins |
| PR #27407 | Declare tooling/test devDeps resolved by hoisting |
| PR #27481 | Declare esbuild in @strapi/strapi and @strapi/upgrade |
| PR #27743 | Declare deps that published .d.ts files rely on |
| Issue #27744 | Full audit of type declaration dependency gaps |