Editor’s note: This article was last updated by Muhammed Ali on 31 May 2024. It now includes information on managing aliases in monorepos, which often contain multiple packages, each of which potentially requiring its own set of path aliases. It also now covers advanced use cases for path aliases, such as dynamic aliasing for scalable apps and handling projects with multiple aliases.
React and TypeScript have become popular among developers for building scalable, maintainable, modern web applications. However, as projects grow in size and complexity, import statements can become messy. In particular, the default relative path imports can quickly become long and hinder code readability.
Thankfully, there is a solution to this problem: configuring path aliases. In this article, we will explore how to leverage path aliases to enhance the organization and maintainability of your React and TypeScript projects.
To follow along with this tutorial, you’ll need basic knowledge of JavaScript and TypeScript, along with some familiarity with React. You should also have an existing React and TypeScript project, and make sure Node.js is installed on your machine.
In React and TypeScript apps, developers use import statements to bring in functionality from other modules or files. This practice ensures we develop software that is reusable and modular.
Although import statements are useful in this way, they can lead to problems when they aren’t used properly. The code snippet below shows a typical example of this problem:
import React from 'react'; import { Button } from '../../../../components/button'; // long and messy import :( function SomeComponent() { return <Button />; };
As you can see, this code snippet uses relative imports from the current file to import the Button
component. However, this import pattern is messy because it’s importing the component from a deeply nested directory.
For projects that are relatively small in size, this might not pose much of an issue. But as the project grows, typing and reading long import paths becomes tedious.
In addition, refactoring the project becomes challenging when the directory structure of the imported module changes because it’s tightly coupled to the project file structure.
Path aliases let developers define custom shortcuts for import paths, making them cleaner and more intuitive. With path aliases set up, you can have clean and concise imports regardless of the size of the project, as shown in the code snippet below:
import React from 'react'; import { Button } from '@components/button'; // clean and concise import :) function SomeComponent() { return <Button />; };
By setting up path aliases in a React and TypeScript app, you can simplify import statements, improve code navigation, and enhance the overall development experience.
tsconfig.json
fileYou can configure path aliases easily in your project’s tsconfig.json
file. This file is usually found at the root of a TypeScript project.
To configure your path aliases in this file, simply add a paths
property in the compilerOptions
object. Then, you can map path alias names to file paths as shown in the code snippet below:
{ "compilerOptions": { "paths": { "@/*": ["./src/*"] } } }
The above code tells the TypeScript compiler to resolve imports from the @/*
alias to the ./src/*
directory. Once you set up the path alias, you can use it in your import statements.
For example, you can import a Button
component in the src/components
directory directly from anywhere in the project like this:
import { Button } from "@/components/Button"; function App() { return ( <Button>Click Me</Button> ) }
Without a path alias set up, importing the Button
component from another file — for example, src/pages/dashboard/profile/settings/index.tsx
— would look something like this:
import { Button } from '../../../../components/Button'; function Settings() { return ( <Button>Click Me</Button> ) }
You can take this a step further and add more path aliases, which can be beneficial for large projects that store critical parts of the app in well-defined directories. In the tsconfig.json
file, update the paths
field as shown in the following code snippet:
{ "compilerOptions": { "baseUrl" : "./src", "paths": { "@components/*": ["./components/*"], "@ui/*": ["./components/common/ui/*"], "@pages/*": ["./pages/*"], "@hooks/*": ["./hooks/*"], "@api/*": ["./api/*"], "@utils/*": ["./utils/*"], } } }
The baseUrl
field in the code snippet above is used to make the path aliases shorter to write.
Path aliases can help significantly improve the readability and conciseness of import paths in your projects, but they have to be used correctly to maximize their benefits.
Here are some best practices to follow when using path aliases in your React and TypeScript projects:
@components
instead of @c
to define an alias for components stored in the components
directory@components
, stick to a similar naming pattern like @utils
, @api
, etc. This consistency can help other developers understand and remember the aliasesnode_modules
directory. It’s good practice to always use unique names for your path aliases to prevent such conflictsKeep in mind that you should use path aliases wisely and strategically. For example, you should never use path aliases as a quick fix for poor code organization. Instead, it’s crucial that you organize your codebase properly and ensure that your project follows approved standards.
However, TypeScript and build tools such as webpack or esbuild are well-optimized and employ caching mechanisms to reduce the impact of repeated module resolution to counter any potential performance issues.
You may experience a slight performance hit when using path aliases in the rare cases of large and complex projects with deep module hierarchies. But even in those cases, path aliases offer benefits such as improved code organization and maintainability that typically outweigh the impact on performance.
Following these best practices can help you leverage the benefits of path aliases without running into drawbacks and errors.
If you’ve been following this tutorial with a React and TypeScript project set up with Create React App (CRA), you might encounter an error like the following:
This error occurs because webpack can’t resolve imports defined using the path aliases in the tsconfig.json
file.
An easy fix for this error is to update the webpack configuration to resolve the path alias. However, the default CRA setup doesn’t provide support for modifying the webpack configuration without ejecting, which can be inconvenient.
Fortunately, the CRACO package provides a convenient way to modify configurations for ESLint, Babel, and webpack in a CRA project without needing to eject.
CRACO stands for Create React App Configuration Override. This open source project was created to address limitations in customizing and extending CRA.
By default, when you eject a CRA project, you’re responsible for maintaining the configuration files and scripts required for your project to work. This responsibility can make updating and maintaining the project difficult in the future.
CRACO frees you from this burden by providing a single config file for overriding and extending the default configurations of CRA while also retaining its simplicity and abstraction layer. We’ll explore how to do so in the next section.
Let’s first install CRACO in the CRA project by running the command below:
npm i -D @craco/craco
Next, create a craco.config.js
file at the root of the project and add the following configuration:
const path = require('path'); module.exports = { webpack: { alias: { '@': path.resolve(__dirname, 'src'), }, }, };
Update the scripts
field in the package.json
file with the following to use the craco
CLI instead of react-scripts
for the start
, build
, test
, and eject
scripts:
"scripts": { "start": "craco start", "build": "craco build", "test": "craco test", "eject": "craco eject" }
After you complete the steps above successfully, webpack should be correctly configured to work with your defined path aliases. This configuration will ensure no errors will occur due to unresolved import paths.
In monorepo projects, which often contain multiple packages, each package might need its own set of path aliases. Managing aliases in such setups requires additional configuration to ensure each package can resolve its dependencies correctly.
To handle aliases in a monorepo setup, you can configure the tsconfig.json
file in the root directory and individual tsconfig.json
files in each package directory. The root tsconfig.json
should reference the path aliases for all packages, while each package-specific tsconfig.json
should inherit from the root configuration.
Here is a root tsconfig.json
example:
{ "compilerOptions": { "baseUrl": ".", "paths": { "@shared/*": ["packages/shared/*"], "@app/*": ["packages/app/*"], "@lib/*": ["packages/lib/*"] } }, "include": ["packages/**/*"] }
Here is a package-specific tsconfig.json
example (e.g., packages/app/tsconfig.json
):
{ "extends": "../../tsconfig.json", "compilerOptions": { "baseUrl": "./src" }, "include": ["src"] }
This setup allows each package to resolve its dependencies using the path aliases defined in the root configuration while maintaining individual configurations tailored to each package.
Path aliases can be used for more advanced use cases in scalable apps and projects with multiple aliases.
One such use case is dynamic aliasing. In large-scale applications, you might need to dynamically change the alias paths based on different environments (e.g., development, staging, production). This can be achieved by using environment-specific configuration files and a build script to dynamically modify the tsconfig.json
file.
Another use case is in projects with multiple aliases. Projects with modular architectures often benefit from multiple path aliases. For example, you can define aliases for different layers of your application (e.g., @services
, @controllers
, @models
) to clearly separate concerns and improve code organization.
Here is an example of dynamic aliasing using environment variables:
// tsconfig.dev.json { "compilerOptions": { "baseUrl": "./src", "paths": { "@config/*": ["./config/dev/*"] } } } // tsconfig.prod.json { "compilerOptions": { "baseUrl": "./src", "paths": { "@config/*": ["./config/prod/*"] } } }
This is the build script to switch configuration:
const fs = require('fs'); const env = process.env.NODE_ENV || 'development'; const tsConfig = env === 'production' ? 'tsconfig.prod.json' : 'tsconfig.dev.json'; fs.copyFileSync(tsConfig, 'tsconfig.json');
By incorporating these advanced use cases, you can further enhance the flexibility and scalability of your React and TypeScript projects.
Configuring path aliases in a React and TypeScript app is a powerful technique that can significantly improve code readability and organization.
By defining custom shortcuts for import paths, you can simplify your imports and make them more intuitive. Path aliases not only make code navigation easier but also reduce the likelihood of errors caused by complex relative paths.
Embracing path aliases in your React and TypeScript projects can significantly enhance the development experience, leading to cleaner and more maintainable code in the long run.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowEfficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.
Design React Native UIs that look great on any device by using adaptive layouts, responsive scaling, and platform-specific tools.
Angular’s two-way data binding has evolved with signals, offering improved performance, simpler syntax, and better type inference.
Fix sticky positioning issues in CSS, from missing offsets to overflow conflicts in flex, grid, and container height constraints.
7 Replies to "Using path aliases for cleaner React and TypeScript imports"
Nice job!
When importing files like this, how would you use it when using the import statement for lazy loading?
React may not understand that path and would throw an error.
Hello!
Your readers may be interested in this package (caveat; it’s mine):
https://github.com/davestewart/alias-hq/
It makes your tsconfig aliases available to other packages such as Vite, Webpack, Jest, Node, etc using a one liner; no more refactoring aliases for each framework!
This is a bad idea. If your imports are getting ugly, fix your code organization instead. This has performance impacts that are awful, and goes against the js standard which presents issues with all kinds of libraries and tooling. Research how module resolution works, it’s an expensive operation. Devs need to quit fighting standards and trying to turn js into Java etc…
Hi Joshua, thanks for this feedback. We’ve added some more information in the “Best practices” section to emphasize the importance of following approved standards and organizing code properly rather than using path aliases as a quick fix. While it’s true that path aliases can sometimes impact performance — especially when not used wisely — we also added some clarifications around how TypeScript and build tools help counter potential performance issues. We appreciate your taking the time to read this article and share your thoughts!
I agree, if you need path aliases to hide the fact that you have a poorly-designed project structure, you have a bigger problem on your hands.
I agree alias imports are nice, but I don’t find renaming functionality to be working in VSCode. It seems not supported. Also found this issue: https://github.com/microsoft/TypeScript/issues/27959 (amongst many!). This decreases productivity drastically. Anybody found a way around this?