Leveling Up Your React TypeScript Game: Mastering Path-Aliases for Cleaner and More Robust Code

Leveling Up Your React TypeScript Game: Mastering Path-Aliases for Cleaner and More Robust Code

The importing processes get confusing as your TypeScript React project expands, but everything is nicely organized in its folder. Mainly if you are using a react-bulletproof project structure, this is true. We have our components neatly in one place, functions in another, pages, routes, etc.; you name it. Though keeping track of files when we organize them is easy, the problem arises when we try to import them throughout our project. After some time spent on organization, your project looks more like this:

And if you want to import the function UrlParamExtractor to Character.tsx, your import looks like this:

Our problem is that the path contains far too many ../../../ statements, and if we aim for a higher structural level, the path may get even longer.

This is incredibly annoying because we will sometimes import from different levels, forcing us to attempt to figure out how to go to that other path by figuring out where we are.?

Ready to improve your imports? Let’s get started!

Spoiler Alert: After this article, this is how your imports are going to look like

There are several ways to solve this issue. Surprisingly, even in 2023, when we have React 18, CRA v5, and Vite configurations, path aliases are still not supported out of the box.

Setting up a Path Alias

Using a path alias allows us to define a word or a path to represent its absolute path instead of its relative path. To configure this, we can use the default configuration file you set up when creating the typescript project.

Using Create-React-App

To configure an alias in your default CRA project, you first need to create a new file named “tsconfig.paths.json” at the root of the project (not inside the src folder). After making the file, paste the following code inside of it.

{
    "compilerOptions": {
        "baseUrl": ".",
        "paths": {
            "@/*": [
                "./src/*"
            ]
        }
    }
}

We must attach this to our main “tsconfig.json” file so the project configuration can read our newly added options. To do that, we can extend our freshly created file inside tsconfig.json. Go to your “tsconfig.json” and paste the following code after “compilerOptions”. (You might already have the “include” option. In that case, you can skip those lines.)

"include": [
    "src"
  ],
  "extends": "./tsconfig.paths.json"

You might wonder why we omitted this directly inside our “tsconfig.json” file. Well, at first, I did too.

This is because tsconfig.json gets cleaned away by React (and later, craco) at startup, so it only retains the settings necessary to function correctly. One of the properties it deletes upon startup is the “paths” property.

Creating path configurations in a separate file and extending it to the main file allow us to ignore that behavior.

Once you set up the above (restarting the typescript server in VSCode might be required), VSCode will immediately recognize new paths in Intellisense. Now you can import all your paths correctly, as mentioned in that spoiler image above.
But what’s the catch? Well, when you import new paths, you will see your React app is no longer working and gives you errors like this.

Did we do something wrong? Yes…. but no. This is entirely normal.

We need to make some additional configurations to our React App. You can achieve this using either craco or react-app-rewired.

Since CRA is using Webpack under the hood, we need to modify Webpack configs to allow it to read the new aliases we just created.

You can pick one of the methods below.

  • Resolve using CRACO

    First, you need to add CRACO to your project.

      yarn add @craco/craco
    

    Once you install, you need to create a file called “craco.config.js” in the project’s root (the same level we created the “tsconfig.paths.json” file). Inside the “craco.config.js” file, put the following configurations.

      const path = require('path');
      module.exports = {
        webpack: {
          alias: {
            '@': path.resolve(__dirname, 'src'),
          },
        },
      };
    

    And as the last step, you need to modify package.json scripts to the following.

      "scripts": {
          "start": "craco start",
          "build": "craco build",
          "test": "craco test",
          "eject": "craco eject"
       }
    
  • Resolve using REACT-APP-REWIRED

      yarn add -D react-app-rewired
    

    Once you install, you need to create a file called “config-override.js” in the project’s root (the same level we created the “tsconfig.paths.json” file). Inside the “config-override.js” file, put the following configurations.

      const path = require('path');
    
      module.exports = function override(config) {
       config.resolve = {
        ...config.resolve, //remove if not needed
        alias: {
         ...config.alias, //remove if not needed
         '@': path.resolve(__dirname, 'src'),
        }
       }
       return config;
      }
    

    Similar to craco, you have to modify your package.json scripts to adapt to react-app-rewired configurations.

      "scripts": {
       "start": "react-app-rewired start",
       "build": "react-app-rewired build",
       "test": "react-app-rewired test",
       "eject": "react-scripts eject"
       }
    

    Now when you run “yarn start” voila!! Its works just fine with our cleaner imports.

    Let me know if you had any issues while following this guide, I’ll be happy to help you out.