Question
TypeScript tsconfig Paths: How Path Mapping Works with Webpack
Question
I am learning about path mapping in tsconfig.json, and I want to use it to avoid long relative imports such as:
import { Something } from "../../../../../lib/src/[browser/server/universal]/...";
My project structure is unusual because it is a monorepo containing multiple projects and libraries. The projects are grouped by company and by target such as browser, server, and universal.
How can I configure tsconfig.json so that I can write imports like this instead?
import { Something } from "lib/src/[browser/server/universal]/...";
Also, is tsconfig.json enough by itself, or do I also need to update my Webpack configuration?
Short Answer
By the end of this page, you will understand how TypeScript path mapping works, how to configure baseUrl and paths in tsconfig.json, and why tools like Webpack usually need matching alias configuration. You will also learn the difference between helping the TypeScript compiler find files and helping your runtime or bundler resolve imports correctly.
Concept
TypeScript supports path mapping through the compilerOptions.paths setting in tsconfig.json. This feature lets you replace long relative import paths with shorter, clearer aliases.
For example, instead of this:
import { Something } from "../../../../../lib/src/browser/utils/something";
You can write something like this:
import { Something } from "@lib/browser/utils/something";
To make that work, TypeScript usually needs two settings:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@lib/*": ["lib/src/*"]
}
}
}
Why this matters
Mental Model
Think of import paths like addresses.
A relative import such as:
../../../../../lib/src/browser/tool
is like giving turn-by-turn directions from your current location.
A path alias such as:
@lib/browser/tool
is like giving a saved place name such as "Office".
TypeScript can be taught that "Office" means lib/src. But if Webpack or Node has never heard of that nickname, it will not know where to go.
So:
baseUrlandpaths= teaching TypeScript the nickname- Webpack alias = teaching the bundler the same nickname
- Runtime support = teaching the running app the same nickname if needed
Syntax and Examples
The basic tsconfig.json setup looks like this:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"lib/*": ["lib/src/*"]
}
}
}
What each part means
baseUrl: the starting directory for non-relative module namespaths: custom alias ruleslib/*: the alias pattern used in importslib/src/*: the actual folder TypeScript should look in
Example import
With this config:
{
"compilerOptions": {
"baseUrl":
Step by Step Execution
Consider this tsconfig.json:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@lib/*": ["lib/src/*"]
}
}
}
And this import:
import { formatDate } from "@lib/utils/date";
Step by step
-
TypeScript sees a non-relative import:
@lib/utils/date. -
It checks
baseUrlandpathsintsconfig.json. -
It finds a matching pattern:
- alias pattern:
@lib/*
- alias pattern:
Real World Use Cases
Path mapping is especially useful in large or multi-package projects.
Monorepos
A monorepo may contain:
- shared UI libraries
- server utilities
- browser-only helpers
- universal modules used everywhere
Aliases help keep imports readable:
import { Button } from "@lib/ui/Button";
import { logger } from "@server/logging/logger";
Frontend applications
Instead of:
import { formatPrice } from "../../../../shared/utils/formatPrice";
You can use:
import { formatPrice } from "@shared/utils/formatPrice";
Backend services
In a Node or API project, aliases make service-layer imports cleaner:
import { validateUser } from "@core/validation/user";
import { db } from ;
Real Codebase Usage
In real projects, developers rarely use paths alone. They usually build a consistent module resolution strategy across tools.
Common patterns
1. Feature-based aliases
Instead of aliasing one huge root folder, teams often define aliases by responsibility:
{
"paths": {
"@components/*": ["src/components/*"],
"@services/*": ["src/services/*"],
"@utils/*": ["src/utils/*"]
}
}
This makes architecture more obvious in imports.
2. Shared aliases across apps in a monorepo
A root tsconfig.base.json may define aliases once, and child projects extend it:
{
"compilerOptions": {
Common Mistakes
1. Forgetting baseUrl
Many paths setups require baseUrl.
Broken example:
{
"compilerOptions": {
"paths": {
"@lib/*": ["lib/src/*"]
}
}
}
Safer setup:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@lib/*": ["lib/src/*"]
}
}
}
2. Expecting to configure Webpack automatically
Comparisons
| Approach | Example | Best for | Pros | Cons |
|---|---|---|---|---|
| Relative imports | ../../utils/math | Nearby files | Simple, no extra config | Becomes messy in deep folders |
| TypeScript path aliases | @lib/utils/math | Large TypeScript projects | Readable, maintainable | Needs config in multiple tools |
| npm package imports | lodash | External dependencies or published internal packages | Standard module resolution | Requires package setup |
| Monorepo workspace packages | @company/shared | Large multi-package repos |
Cheat Sheet
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@lib/*": ["lib/src/*"],
"@browser/*": ["lib/src/browser/*"],
"@server/*": ["lib/src/server/*"],
"@universal/*": ["lib/src/universal/*"]
}
}
}
Quick rules
- Use
baseUrlwithpaths. @alias/*maps toreal/path/*.- The should usually appear on both sides.
FAQ
Is tsconfig.json enough to use path aliases?
Usually not. It is enough for TypeScript type checking and editor support, but bundlers like Webpack often need matching alias configuration.
Do I need baseUrl when using paths?
In most common setups, yes. It defines the base location from which non-relative imports are resolved.
Why does TypeScript compile, but Webpack says module not found?
Because TypeScript and Webpack resolve modules separately. Your alias is likely configured in tsconfig.json but not in Webpack.
Should I use lib/* or @lib/*?
@lib/* is usually clearer because it looks like a deliberate alias instead of a package name.
Can I use path aliases in a monorepo?
Yes. They are very common in monorepos for shared code, especially when combined with a base tsconfig file.
Do path aliases change the emitted JavaScript import paths?
Not by themselves in a way that solves runtime resolution everywhere. They mainly affect how TypeScript understands module paths.
Can I create separate aliases for browser, server, and universal code?
Yes. This is a practical way to make environment boundaries clear, for example @browser/*, , and .
Mini Project
Description
Create a small TypeScript project that replaces deep relative imports with path aliases. This demonstrates how to configure tsconfig.json and Webpack so both the compiler and bundler understand the same import paths.
Goal
Set up a working alias-based import system for shared TypeScript code in a multi-folder project.
Requirements
- Create a
lib/srcfolder with at least one utility module. - Configure
tsconfig.jsonwithbaseUrland a path alias. - Import the utility module using the alias instead of a relative path.
- Add matching alias resolution in Webpack.
- Build successfully without using
../../in the import.
Keep learning
Related questions
@Directive vs @Component in Angular: Differences, Use Cases, and When to Use Each
Learn the difference between @Directive and @Component in Angular, including use cases, examples, and when to choose each.
Angular (change) vs (ngModelChange): What’s the Difference?
Learn the difference between Angular (change) and (ngModelChange), when each fires, and which one to use in forms and inputs.
Angular @ViewChild Returning Undefined: Lifecycle, Child Components, and Fixes
Learn why Angular @ViewChild can be undefined, when it becomes available, and how to access child components correctly using lifecycle hooks.