DEV Community

Cover image for Understanding relative and absolute imports in Next.js
Matt Angelosanto for LogRocket

Posted on • Originally published at blog.logrocket.com

Understanding relative and absolute imports in Next.js

Written by Ibadehin Mojeed✏️

Building a component-based project, like with React’s Next.js framework, requires importing modules or component files to create an entire user interface.

When we import modules to use in a file, we must know how to determine where the imported modules are located in relation to the current file. To do this, we must understand what relative and absolute imports are.

In this lesson, we will discuss relative and absolute imports and learn how to implement them in a Next.js application. We will cover:

Next.js relative imports

In a relative import, the file's import path should be relative to where the import statement is. Relative imports generally start with ./, ../, and so on.

Let’s consider a typical Next.js file structure:

project
    ├── components
          ├── Footer.js
          ├── Header.js
          └── Layout.js
Enter fullscreen mode Exit fullscreen mode

Usually, when we have files like Header.js and Footer.js containing content that we can share across multiple pages, we would import them into a Layout.js file to compose the web pages. If we import those component files correctly in Layout.js, we will have the following imports in the top section like so:

import Header from './Header';
import Footer from './Footer';
Enter fullscreen mode Exit fullscreen mode

The file extension defaulted to .js, so we ignored it in the file path.

Now, let’s break down the path in '':

Adding ./ in the path means JavaScript will look for a file relative to the “current” directory. This is because the Header.js and Footer.js files live in the same folder as the Layout.js — in this case, the components folder.

Also, we have a pages/_app.js file in Next.js that lets us use the Layout component to wrap the top-level Component. So, let’s consider the following updated structure:

project
    ├── components
          ├── Footer.js
          ├── Header.js
          └── Layout.js     
    │── pages
          ├── _app.js
          ├── ...
Enter fullscreen mode Exit fullscreen mode

Now, importing the Layout component of the Layout.js file inside the pages/_app.js file will look like so:

import Layout from '../components/Layout';
Enter fullscreen mode Exit fullscreen mode

Using ../ in the relative file path inside the pages/_app.js file lets us step out from the current directory — pages — so we can go into the components folder to access the Layout.js file.

Let’s see another example. Imagine we have the following file structure:

project
   ...
    │── pages
          ├── blog
              │── index.js
              └── ...
          ├── _app.js
          ├── ...    
    │── styles
          ├── Blog.module.css
          ├── ...
Enter fullscreen mode Exit fullscreen mode

Let’s break down how we can import the Blog.module.css file inside the pages/blog/index.js file:

First, we will step out of the current directory — the blog directory — and move into the parent directory — pages — using ../ in our file path.

Then, from the pages directory, we will also step out into its parent directory — the root — so the relative path now looks like so: ../../.

We will then go into the styles directory from the root, so the path looks like ../../styles.

Finally, we can access the Blog.module.css, so we have: ../../styles/Blog.module.css.

If we put the above steps into action, the import and file path will look like so:

import styles from '../../styles/Blog.module.css';
Enter fullscreen mode Exit fullscreen mode

In summary, we use ./ in a file to reference a module that lives in the same directory as that file. Likewise, we use ../ in a file to reference a module that lives in the parent directory, ../../ in a file to reference a module in the directory above the parent, and so on.

Drawback of relative imports

Relative imports are not always friendly. Actually, they can be quite confusing! As we’ve seen above, we had to keep careful track of the level of the directory we are currently in.

Additionally, relative imports can result in a poor developer experience, especially in a complex project. If our application grows, we may end up getting a path that looks like so:

'../../../styles/Blog.module.css';
Enter fullscreen mode Exit fullscreen mode

The path can look even more complex for a very deeply nested path.

The problem may worsen if we change the file location, as this may require us to update the file paths. To improve the developer experience, we will learn how to configure a Next.js project to support absolute imports.

Next.js absolute imports

An absolute import provides a straightforward way to import modules. This import type specifies a path starting from the project’s root.

Now, instead of worrying about keeping track of directory levels like so, as in the case of relative imports:

'../../../styles/Blog.module.css';
Enter fullscreen mode Exit fullscreen mode

We will have a cleaner way that looks like this:

'styles/Blog.module.css';
Enter fullscreen mode Exit fullscreen mode

In the code above, I am assuming that the styles directory exists at the project root.

We can also use an alias that looks like so:

'@styles/Blog.module.css';
Enter fullscreen mode Exit fullscreen mode

Let’s take a closer look at using absolute imports in a Next.js project.

Creating a jsconfig.json file

The jsconfig.json file lets us specify a base directory for a project. Since version 9.4, Next.js allows us to use this file to identify the root files and perform path mapping needed for absolute imports.

To get started, we will create a jsconfig.json in the root of our project and add the following configuration:

{
  "compilerOptions": {
    "baseUrl": "."
  }
}
Enter fullscreen mode Exit fullscreen mode

Note that if we were using TypeScript, we would add the code in tsconfig.json instead.

The baseUrl option lets us specify the base directory to resolve modules. By assigning a ., JavaScript or TypeScript will look for files starting in the same directory as the jsconfig.json — that is, the root directory.

For that reason, our earlier relative import example, which looked like this:

import styles from '../../styles/Blog.module.css';
Enter fullscreen mode Exit fullscreen mode

Will now look like the following:

import styles from 'styles/Blog.module.css';
Enter fullscreen mode Exit fullscreen mode

Again, in the code above, I am assuming that the styles directory exists at the project root. Note that anytime we modify the jsconfig.json or tsconfig.json files, we must restart the dev server.

Configuring the baseUrl

It is common for developers to create a src folder in the project root to hold the working files. Let’s consider the following structure:

project
   ...
    │── src
        ├── components    
        │── pages
        │── styles
        │── ...
    
    │── jsconfig.json
Enter fullscreen mode Exit fullscreen mode

In this case, we can tell JavaScript or TypeScript to look for files starting at the src folder by updating the baseUrl to point to src:

{
  "compilerOptions": {
    "baseUrl": "src"
  }
}
Enter fullscreen mode Exit fullscreen mode

The src directory as specified in the file must be relative to the project root. Now, this update will allow absolute imports from the src directory.

Configuring module aliases to simplify Next.js absolute imports

For more significant projects with different levels of deeply nested directories containing files, we may want to create custom module aliases to match different directories instead of matching only the base directories.

Consider the following structure:

project
   ...
    │── src
        ├── components 
               │── homePage
                     │── Hero.js
                     │── Testimonial.js
                     │── ...               
               │── blogPage
               │── ...  
        │── pages
             │── index.js
             │── ... 
        │── styles
        │── ...
    
    │── jsconfig.json
Enter fullscreen mode Exit fullscreen mode

By using a relative import, as we've learned, we can import the Testimonial.js component file from inside pages/index.js like so:

import Testimonial from '../components/homePage/testimonial'
Enter fullscreen mode Exit fullscreen mode

We also learned to simplify the above process using the absolute import, so we have the following:

import Testimonial from 'components/homePage/testimonial'
Enter fullscreen mode Exit fullscreen mode

Now, by configuring module aliases, we can further simplify the absolute import so we can have the following:

import Testimonial from '@homePage/testimonial' 
Enter fullscreen mode Exit fullscreen mode

Let’s explore how to do so using a paths option.

The paths option

Adding a paths option in the configuration lets us configure module aliases. Considering the file structure above, we will update the config file to include paths entries. By starting from the root, the jsconfig.json file will look like so:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@homePage/*": ["src/components/homePage/*"],
      "@blogPage/*": ["src/components/blogPage/*"],
      "@styles/*": ["src/styles/*"],
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The paths object contains entries that are resolved relative to the baseUrl. In the above code, the entries are relative to the project root.

If we specify the src as the baseUrl, the path entries will look like so:

{
  "compilerOptions": {
    "baseUrl": "src",
    "paths": {
      "@homePage/*": ["components/homePage/*"],
      "@blogPage/*": ["components/blogPage/*"],
      "@styles/*": ["styles/*"],
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

In this case, the entries are relative to the src directory, which is then relative to the project root. Either of the above code blocks will work.

The above configuration creates path aliases for all files in the:

  1. homePage folder using @homePage
  2. blogPage folder using @blogPage
  3. styles folder using @styles

So instead of using components/homePage/Hero, we would use @homePage/Hero. Code editors like VS Code will also know how to provide proper IntelliSense for path autocomplete.

How to troubleshoot Next.js absolute imports not working

If you’ve followed this lesson up to this moment, you shouldn’t experience issues with absolute imports. However, we will mention two steps that often help resolve or prevent some common pitfalls that users run into while configuring Next.js for absolute imports.

First, we must ensure that our Next.js version is at least v9.4. Then, we must always restart the Next.js project if we modify the jsconfig.json config file. Following these two steps should keep you from running into issues with Next.js absolute imports not working, or help you resolve existing errors.

Migrating a Next.js project from using relative imports to absolute imports

So far, we’ve covered all we need to know regarding relative and absolute imports. This section will implement what we’ve learned in a Next.js application.

In a different blog post, we discussed how to add an RSS feed to a Next.js app. In that lesson, we worked with a Next.js project that used a relative import to load files. We will refactor the module's path to use absolute imports.

Let’s clone the project and follow the steps to support absolute imports after.

Clone a Next.js project

Clone the project using the following command:

git clone https://github.com/Ibaslogic/nextjs-mdx-blog-rss
Enter fullscreen mode Exit fullscreen mode

Next, cd into the project and run the command that installs dependencies to the local node_modules folder:

cd nextjs-mdx-blog-rss

npm install
# or
yarn
Enter fullscreen mode Exit fullscreen mode

Finally, run the project:

npm run dev
# or
yarn dev
Enter fullscreen mode Exit fullscreen mode

The project should be up and running at http://localhost:3000.

Project structure

If we open the source code, we should have a file structure closer to the following:

nextjs-mdx-blog-rss
   ...
    ├── components
          ├── Footer.js
          ├── Header.js
          ├── Layout.js
          └── MdxComponents.js
    ├── pages
          ├── api
          ├── blog
              ├── [slug].js
              └── index.js
          ├── _app.js
          ├── ...
          └── index.js
    ├── posts
    ├── public
    ├── styles
    ├── utils
          ├── generateRSSFeed.js
          ├── mdx.js
    ├── menuItems.js
   ... 
Enter fullscreen mode Exit fullscreen mode

Add support for absolute imports

If we navigate the project folder and open the files, we have used relative imports to include file content in another file. To add support for absolute imports, we’ll use the following steps.

First, add a jsconfig.json file in the root of the project and add the following code:

{
  "compilerOptions": {
    "baseUrl": "."
  }
}
Enter fullscreen mode Exit fullscreen mode

Next, save the file and restart the dev server.

We can now define absolute imports from the project’s root with the above code. In other words, all the imports are now relative to the project's root. So, we can now import modules directly from the root directories like components, styles, and utils.

For instance, look for MDXComponents import in the blog/[slug].js file and update from using a relative import, which is shown below:

import MDXComponents from '../../components/MdxComponents';
Enter fullscreen mode Exit fullscreen mode

…to instead use an absolute import, like so:

import MDXComponents from 'components/MdxComponents'; 
Enter fullscreen mode Exit fullscreen mode

Let’s do the same for the other imports. We will configure an optional module alias from the next step to further simplify absolute imports.

Let’s open the jsconfig.json file and update the configuration so we have the following:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@components/*": ["components/*"],
      "@styles/*": ["styles/*"],
      "@utils/*": ["utils/*"],
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

We’ll save the file and restart the dev server.

Now, we can specify a path like so:

import MDXComponents from '@components/MdxComponents';
Enter fullscreen mode Exit fullscreen mode

Instead of this:

import MDXComponents from 'components/MdxComponents'; 
Enter fullscreen mode Exit fullscreen mode

Let’s again do the same for the other imports. We’ll need to follow the next three steps if we want to put the working files in an optional src folder.

Create an src folder in the project root and move the components, pages, styles, and utils folders, along with the menuItems.js file, inside the src folder.

Next, update the configuration baseUrl to point to the src:

{
  "compilerOptions": {
    "baseUrl": "src",
    "paths": {
      "@components/*": ["components/*"],
      "@styles/*": ["styles/*"],
      "@utils/*": ["utils/*"],
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Restart the dev server.

Let’s ensure the project works as expected. See the final project source code here.

Conclusion

As we’ve learned, relative imports don’t need any configuration like absolute imports. However, relative imports can sometimes be confusing and may result in a poor developer experience. We have a more precise and concise way to import modules in Next.js with absolute imports.

In this lesson, we discussed relative and absolute import types and how to implement them in a Next.js project. I hope you enjoyed reading this guide. If you have any thoughts, you can share them in the comment section.


LogRocket: Full visibility into production Next.js apps

Debugging Next applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket signup

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your Next app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your Next.js apps — start monitoring for free.

Top comments (0)