Fix Cannot find module defined in tsconfig paths at runtime
Why tsc compiles an @alias import cleanly and Node still crashes on it, and five runtime fixes tested on Node 22 with TypeScript 5.9.3 and 7.0.2.
TL;DR#
paths in tsconfig.json only tells the TypeScript compiler where to find a module while it type checks. tsc writes your alias into the output unchanged, so node dist/src/index.js calls require() on the alias itself and Node throws Cannot find module. Adding include or moving baseUrl does not change that. Fix it at runtime: preload tsconfig-paths/register (for ts-node, or for compiled code with TS_NODE_BASEURL), rewrite the emitted imports with tsc-alias after tsc, run with tsx, or swap the aliases for package.json "imports" (#data/*), which Node resolves natively.
The error#
The Stack Overflow question sets up aliases for a mock server. Its tsconfig.json has "baseUrl": "./src" and maps @project/server/data/* to ../server/src/json/*, and the project fails with:
Error: Cannot find module '@project/server/data/accounts/accountsList'The error was reproduced on 19 October 2026 in the official node:22-alpine image (Node v22.23.3) with TypeScript 5.9.3, on a project with the same folder layout. It keeps the question's baseUrl and paths, but raises target to es2019 and adds strict and outDir, because the question's ES5 target is rejected by the current compiler (more on that below). tsc exits with code 0. Running the output gives the error from the question (code frame and stack trimmed):
$ node dist/src/index.js
Error: Cannot find module '@project/server/data/accounts/accountsList'
Require stack:
- /work/dist/src/index.js
code: 'MODULE_NOT_FOUND',Why it happens#
The compiled file still contains the alias, word for word. Running grep -n require dist/src/index.js on the output shows lines 3 and 4 passing @project/server/data/accounts/accountsList and @project/app/modules/greet to require(), exactly as they were written in src/index.ts. Nothing was rewritten.
That is by design. The tsconfig reference for paths states: "Note that this feature does not change how import paths are emitted by tsc, so paths should only be used to inform TypeScript that another tool has this mapping and will use it at runtime or when bundling." The module resolution reference puts it more bluntly: it is "very easy to create path aliases that appear to work in TypeScript but will crash at runtime".
Node's CommonJS loader treats any bare specifier, one that does not start with ./, ../ or /, as a package name. As the tsconfig-paths README puts it, Node "will only look in the node_modules folders all the way up to the root of the filesystem". There is no such package, so it throws MODULE_NOT_FOUND. The baseUrl reference adds that the option "was designed for use in conjunction with AMD module loaders in the browser", and that since TypeScript 4.1 it is no longer needed for paths.
ts-node behaves the same way, and says so. Its README explains that "paths" are meant to describe mappings the runtime already performs, "For this reason, ts-node does not modify node's module resolution behavior to implement "paths" mappings." Running the source directly fails too:
$ npx ts-node src/index.ts
Error: Cannot find module '@project/server/data/accounts/accountsList'
Require stack:
- /work/src/index.tsThe include answer does not change the runtime#
The second-ranked answer moves baseUrl to ., rewrites both paths entries relative to the root and adds "include": ["./src", "./server"]. That is a tidier config, but it was tested as a negative control: tsc still exits 0, and node dist/src/index.js throws the identical Cannot find module '@project/server/data/accounts/accountsList'. Nothing in tsconfig.json can reach Node at run time.
Fix#
Pick the step that matches how you run the code. Each was run on its own against the failing project above.
-
Running
.tsfiles with ts-node: preloadtsconfig-paths. tsconfig-paths readspathsfromtsconfig.jsonand patches Node's module loading so the alias resolves to a real file.bashnpm install -D tsconfig-paths npx ts-node -r tsconfig-paths/register src/index.tsOutput:
accounts loaded: 2. This ran with TypeScript 5.9.3 installed beside ts-node; with the 7.0.2 compiler that npm installs by default today, ts-node crashed before reaching your code (see the TypeScript 7 section below). To avoid typing the flag, put it intsconfig.json, as the top answer and the ts-node README do. This variant also printedaccounts loaded: 2(excerpt; the rest ofcompilerOptionsstays as it was):json{ "ts-node": { "require": ["tsconfig-paths/register"] }, "compilerOptions": { "baseUrl": "./src", "paths": { "@project/server/data/*": ["../server/src/json/*"] } } } -
Running compiled
distwith tsconfig-paths: point it atdist. Preloading the hook in front of plainnodeis not enough on its own. The test run still threwCannot find module, withtsconfig-paths/lib/register.jsnow in the stack, because the mapping leads toserver/src/json/..., where only a.tsfile exists. The tsconfig-paths README documentsTS_NODE_BASEURLto overridebaseUrl. Point it at the compiled copy ofsrc:bashTS_NODE_BASEURL=./dist/src node -r tsconfig-paths/register dist/src/index.jsThis printed
accounts loaded: 2. Note thattsconfig-pathsis now a runtime need, so it belongs independencies, notdevDependencies, if the production install omits dev packages. -
Shipping plain JavaScript: rewrite the imports with tsc-alias after tsc. tsc-alias replaces each alias in the emitted files with a relative path, so the output needs no hook at all.
bashnpm install -D tsc-alias npx tsc && npx tsc-aliasThe compiled file afterwards:
text3:const accountsList_1 = require("../server/src/json/accounts/accountsList.js"); 4:const greet_1 = require("./modules/greet.js");node dist/src/index.jsthen printedaccounts loaded: 2. Wire both commands into the build script ("build": "tsc && tsc-alias") so a deploy never ships un-rewritten files. -
Running TypeScript directly without ts-node: use tsx. tsx resolved both aliases from
tsconfig.jsonwith no extra package:npx tsx src/index.tsprintedaccounts loaded: 2(tsx v4.23.15, in a project on TypeScript 7.0.2). Steps 1, 2, 3 and 5 as written above ran on TypeScript 5.9.3; the TypeScript 7 section below covers 7.0.2. -
Removing the alias problem altogether:
package.json"imports". The module resolution reference recommends package.json"imports""as a standard replacement for conveniencepathsaliases". Node resolves#specifiers itself (subpath imports), and TypeScript resolves them whenmoduleResolutionisnode16,nodenextorbundler. For a local project it maps thedisttargets back to the sources usingoutDirandrootDir; the reference adds that "using"imports"usually requires an explicitrootDirto be set", which is whyrootDirappears below. The testedpackage.json:json{ "name": "mock-server", "version": "1.0.0", "private": true, "imports": { "#data/*": "./dist/server/src/json/*.js", "#modules/*": "./dist/src/modules/*.js" } }And the
tsconfig.json, withpathsandbaseUrlgone:json{ "compilerOptions": { "module": "nodenext", "moduleResolution": "nodenext", "rootDir": ".", "outDir": "dist", "strict": true, "target": "es2022" }, "include": ["src", "server"] }With the imports written as
#data/accounts/accountsListand#modules/greet,tsckept them as they are andnode dist/src/index.jsprintedaccounts loaded: 2. These targets point atdist, so this was tested on compiled output after a build only, not under ts-node or tsx. The same reference warns that published libraries must not rely onpaths, and offers"imports"to libraries and apps alike.
The top Stack Overflow answer also suggests module-alias with a _moduleAliases block in package.json for the compiled build. That route was not part of this test; it duplicates every alias in a second file, which the options above avoid.
Verify the fix#
Two checks tell you whether the runtime, not just the compiler, knows the alias:
grep -n require dist/src/index.js
node dist/src/index.jsIf the require lines still show the alias, you need a hook at start-up (step 2). If they show relative paths (step 3) or # specifiers backed by package.json (step 5), plain node must run without one. A clean tsc exit proves nothing here: it was 0 in every failing run above.
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.
TypeScript 7 rejects the question's config before you reach Node#
On 19 October 2026 the latest tag of typescript on the npm registry points to 7.0.2, so a fresh npm install -D typescript gets that version. Run against the question's original tsconfig.json, npx tsc --noEmit stopped on the config itself:
tsconfig.json(3,9): error TS5102: Option 'baseUrl' has been removed. Please remove it from your configuration.
Use '"paths": {"*": ["./src/*"]}' instead.
tsconfig.json(8,29): error TS5108: Option 'moduleResolution=node10' has been removed. Please remove it from your configuration.
tsconfig.json(13,40): error TS5090: Non-relative paths are not allowed. Did you forget a leading './'?
tsconfig.json(17,19): error TS5108: Option 'target=ES5' has been removed. Please remove it from your configuration.The third line follows from the first: without baseUrl, each paths target is resolved relative to tsconfig.json and must be written as a relative path. Only line 13, "modules/*", was flagged; the "../server/src/json/*" target on the next line was accepted. The corrected config drops baseUrl and writes every target relative to tsconfig.json:
{
"compilerOptions": {
"module": "nodenext",
"rootDir": ".",
"outDir": "dist",
"strict": true,
"target": "es2022",
"paths": {
"@project/app/modules/*": ["./src/modules/*"],
"@project/server/data/*": ["./server/src/json/*"]
}
},
"include": ["src", "server"]
}TypeScript 7.0.2 compiled it with exit 0, and the output then failed at run time with the same Cannot find module '@project/server/data/accounts/accountsList'. The compiler changed; the fact that paths never reaches the emitted code did not.
The runtime fixes were then run again in that TypeScript 7.0.2 project:
- tsc-alias (step 3): rewrote both requires to the same relative paths as on 5.9.3, and
node dist/src/index.jsprintedaccounts loaded: 2. - tsx (step 4):
npx tsx src/index.tsprintedaccounts loaded: 2. - package.json
"imports"(step 5, samepackage.jsonandtsconfig.jsonas above): printedaccounts loaded: 2. - tsconfig-paths on
dist(step 2) needs a different override here. With nobaseUrlintsconfig.json, thepathstargets start from the project root, soTS_NODE_BASEURL=./dist/srcstill threwCannot find module, whileTS_NODE_BASEURL=./dist node -r tsconfig-paths/register dist/src/index.jsprintedaccounts loaded: 2. In short, the override must point to the folder insidedistthat mirrors the directory yourpathsare relative to.
One more trap on 7.0.2: ts-node 10.9.2 did not start at all in that project. It crashed in its own config loader with TypeError: Cannot read properties of undefined (reading 'fileExists'), before any module was resolved, and the installed typescript/lib folder held only five files, among them tsc.js. The ts-node route (step 1) was tested only with TypeScript 5.9.3 installed explicitly (npm install -D typescript@5.9.3); on 7.0.2, use tsx or one of the compile-then-run fixes above.
Where the same alias breaks next#
The same "the compiler knows, the tool does not" pattern returns in other runners. Create React App refuses paths outright, covered in compilerOptions.paths must not be set; Next.js reads paths itself while Jest and ESLint need their own wiring, as explained in tsconfig paths not working in Next.js. If ts-node's stack now shows return old(m, filename) with ERR_REQUIRE_ESM, the alias is solved and an ESM-only dependency is the new problem: see ERR_REQUIRE_ESM in ts-node. The dist/src/index.js path in the output above comes from the same compiler layout that makes node dist/main miss its file in a container, which Cannot find module '/dist/main' in a Docker build untangles with rootDir.
If you move the project to "type": "module" while switching to nodenext, the error can change from Cannot find module to ERR_MODULE_NOT_FOUND for relative imports written without .js; continue with ERR_MODULE_NOT_FOUND: relative import missing .js. Whatever runner you settle on, the rule from the TypeScript docs holds: paths describes a mapping that something else must perform, so pick the something else on purpose and check the emitted require lines once.
Further reading: Fix shadcn CLI wrong component paths with extended tsconfig.
Frequently Asked Questions
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
Continue Reading
Fix tsconfig Paths Not Working in Next.js
The @/* alias fails in four different places for four different reasons. A 60-second checklist, then a fix per resolver: Next.js build, TS server, Jest and ESLint.
Fix shadcn CLI wrong component paths with extended tsconfig
Why npx shadcn add creates a literal @renderer folder or writes outside your project in electron-vite and WXT apps, and three fixes reproduced on shadcn 4.22.1.
Fix: compilerOptions.paths Must Not Be Set (Alias Imports)
A tsconfig "paths" alias that works with the TypeScript compiler but breaks Create React App's build. Why CRA specifically forbids it, and three ways to keep clean "@/..." imports anyway.


