The Challenge

You’re using pnpm monorepo workspaces and TypeScript. You want to use path aliases (like @shared/*), but during build you hit conflicts especially when multiple packages or apps define the same alias, or when Next.js/Vite can’t resolve them as expected.

The Anatomy of a Conflict

  • Duplicate Alias Names: Multiple packages define the same alias (e.g. @/ or @shared/*), so it’s ambiguous during build.
  • Alias doesn’t match Package Name: The alias doesn’t match the package name so the bundler or TS can’t resolve it.
  • Bundler/TSConfig mismatch: TS and your bundler (Next.js, Vite, etc.) have different alias configs and you get runtime or build errors.
  • No Unique Package Names: Packages don’t have unique names in package.json so aliasing and resolution is fragile.

Step-by-Step: The Conflict-Free Path

Avoid path alias conflicts in monorepos by following this clean, structured approach to naming, aliasing, and configuration across tools.

1. Give Every Package a Unique Name

json

// packages/shared/package.json{

  “name”: “@yourorg/shared”,

  “version”: “1.0.0”

}

Unique names prevent collision and aid in path mapping.

2. Use Distinct Aliases Per Package

  • Don’t use the same alias (like @/) in multiple projects.
  • Instead, scope each alias to its package:

json

// packages/ui/tsconfig.json{

  “compilerOptions”: {

    “baseUrl”: “.”,

    “paths”: {

      “@ui/*”: [“./src/*”]

    }

  }

}

 

// apps/web/tsconfig.json

{

  “compilerOptions”: {

    “baseUrl”: “.”,

    “paths”: {

      “@/*”: [“./src/*”], // app-local

      “@ui/*”: [“../../packages/ui/src/*”] // shared package

    }

  }

}

This ensures each alias points to exactly one place and avoids overlap.

3. Mirror Alias Config in Your Bundler

js

// next.config.jsconst withTM = require(‘next-transpile-modules’)([‘@yourorg/shared’]);

module.exports = withTM({ /* … */ });

For Vite:

js

// vite.config.tsimport path from ‘path’;

export default {

  resolve: {

    alias: {

      ‘@’: path.resolve(__dirname, ‘src’),

      ‘@ui’: path.resolve(__dirname, ‘../../packages/ui/src’),

    },

  },

};

Always keep alias settings in sync between tsconfig and your bundler.

Also Read: How to Convert HTML Website to ReactJS?

4. Reference the Correct Paths in tsconfig

  • In the consuming app’s tsconfig.json, point aliases to the relative path of the shared package’s source:

json

{  “compilerOptions”: {

    “paths”: {

      “@yourorg/shared/*”: [“../../packages/shared/src/*”]

    }

  }

}

This enables both type-checking and runtime resolution.

5. Avoid Overlapping Wildcard Aliases

  • Never use a catch-all alias like @/* in more than one package it will cause conflicts.
  • Always scope with the package name or a unique prefix.

Also Read: How to Get Theme from Store and Switch the App Theme On react-admin?

Common Pitfalls & Quick Fixes

Pitfall

Example

Fix

Duplicate alias @/ in many All packages use @/*: ./src/* Use @ui/*, @shared/*, etc., per package
Alias not matching package Alias: @shared/*, package: ui Match alias to package: @ui/* for ui package
Only tsconfig, not bundler Alias in tsconfig, not in Vite/Next.js Mirror alias config in both tsconfig and bundler
No unique package name “name”: “shared” “name”: “@yourorg/shared” in package.json

Also Read: How to Import .scss Files Anywhere in Next.js App?

Pro Tips

  • If you use Next.js, always transpile shared packages with next-transpile-modules.
  • If you hit persistent conflicts, consider using relative imports as a last resort—they’re less pretty, but always work.
  • For large monorepos, tools like Nx or Turborepo help manage project references and aliases at scale.
  • Keep your tsconfig and bundler configs DRY: Use a base config and extend it in each package/app.