Ben Fox
Next.js Setup & Config for Testing, Linting, and Absolute Imports
A comprehensive step-by-step guide to configuring Jest, React Testing Library, ESLint, and Path Aliases in a Next.js project.
Next.js is amazing when it comes to installing, learning the framework, and jumping into the code. Its superb documentation & zero-config philosophy make this possible, and not having to think about configuration is wonderfulâŚright up to the point when you want to add some additional configuration.
The configuration I want to add is, technically speaking, useless to your final product. It wonât make it faster, or reduce your bundle size, or add amazing new features.
Howâs that for a hookâŚđ
But itâs important 𤊠Consider this step the first reminder to go slow to go fast (esp. check the second to last paragraph). If you need more convincing, remember when Facebook changed its motto to âmove fast with stable infraâ?
You donât even have to use all of it right away. ESLint and path aliases for absolute imports, once set up, are a free productivity boost. Absolute imports mean no need to worry about how many levels of ../../ are needed to find the component you are trying to import. Linting means no need to worry that a missing ) or } will leave you bashing your head against the wall for 30 minutes wondering why your code doesnât work.
Jest & React Testing Library, on the other hand, require some effort after setup. Testing your code is a good habit to have, and there are some very good resources out there to help you figure out what to test.
You may not start testing much right away â particularly if your UI & functional requirements are subject to frequent, drastic changes â but you should test what you can. If youâre not testing at all, you may want to consider evaluating why youâre putting it off. At least now, with this setup ready to go, youâre more likely to get into the habit.
The problem?
Sounds great right? Youâll have stable infrastructure to boost productivity, linting to enforce consistent coding standards, increasing readability and maintainability, and testing to make sure you donât break stuff 𼳠but getting all of these set up and playing nicely with each other, with Next.js and with your IDE, can be a time consuming exercise in frustration. Itâs also WAY less fun than writing code đŠ
The solution?
This is it! Take the time to set it all up once, before writing any project specific code, to create a codebase that can be easily duplicated for any new projects.
Letâs look at what it takes.
What Weâll Cover
Assumptions
Iâm going to assume you have familiarity running commands in a terminal, and Iâm going to use npm commands. None of the commands are anything fancy, so you should be able to convert to yarn if needed.
Iâm going to jump right in to adding configuration, and wonât dive in to too much detail on any one item â Next.js, React, Jest, React Testing Library, or ESLint âbut I will try to give at least some high-level context for whatâs happening at each step.
Iâm not going to talk about IDE-specific integrations or setup. Iâm using VSCode, and Iâll mention it in a few places. Other IDEs should have similar options, but likely require other specific setup steps. If you run into IDE specific issues let me know and I can see about adding additional notes.
A note before we start
Some of the configuration files we create (jest.config.js .eslintrc, & .babelrc) can be included within package.json rather than using separate files, if that feels cleaner to you. That will require additional wrapping syntax, which you can find at their respective links. The jsconfig.json & jest.setup.js files will have to be separate.
Final Repository
https://github.com/BenjaminWFox/nextjs-base
Next.js: Installing
To start, in your terminal of choice, cd into a folder where you want to install this project. A new subfolder will be created be after you run the setup:
npm init next-app
Give your project a name like nextjs-base (this will also be the folder name), then select Default starter app when prompted to pick a template. Once the install completes, cd nextjs-base into your project folder.
ESLint: Install & Configure
For configuration, letâs start with eslint â thatâll ensure that any future code we write is linted right away and we donât need to go back and make edits. This will also include a plugin for specifically linting React, and another for linting import/export statements:
npm i -D eslint eslint-plugin-react eslint-plugin-import
While thatâs running, create a file .eslintrc at the root of your site. Add the configuration below to this file. There are a ton of options for configuring ESLint. This is, approximately, the bare minimum to get ESLint working with the default Next.js code, without having to make any manual changes:
{
"extends": [
"eslint:all",
"plugin:react/all",
"plugin:import/errors",
"plugin:import/warnings"
],
"env": {
"browser": true,
"es2020": true,
"node": true,
"jest": true
},
"parserOptions": {
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"rules": {
"indent": ["error", 2],
"quotes": ["error", "single"],
"semi": ["error", "never"],
"max-len": 0,
"max-lines-per-function": 0,
"space-before-function-paren": ["error", {
"anonymous": "never",
"named": "never",
"asyncArrow": "always"
}],
"function-call-argument-newline": 0,
"padded-blocks": 0,
"padding-line-between-statements": [
"error",
{ "blankLine": "always", "prev": "*", "next": "return" },
{ "blankLine": "always", "prev": ["const", "let", "var"], "next": "*"},
{ "blankLine": "any", "prev": ["const", "let", "var"], "next": ["const", "let", "var"]}
],
"object-curly-spacing": ["error", "always"],
"one-var": ["error", "never"],
"quote-props": 0,
"react/jsx-indent": [2, 2],
"react/jsx-indent-props": [2, 2],
"react/jsx-filename-extension": 0,
"react/react-in-jsx-scope": 0,
"react/jsx-no-literals": 0,
"react/jsx-one-expression-per-line": 0,
"react/jsx-max-depth": 0
},
"ignorePatterns": ["node_modules/", ".next/"]
}
^ some breakdown of what this is doing:
-
extends sets up a set of base rules to use as a starting point. Using all is probably going to make your life harderâŚbut I would recommend keeping it, and adding specific modifications to rules you donât like. It will give you a good sense of the different ways people might format code. There are all kinds of base configs you could extend instead, from companies (airbnb, facebook) and projects (standard, prettier).
-
env tells ESLint what global variables & special syntax to expect. Since this is for Next.js, weâre adding the browser and node. The es2020 (which is ecmaVersion 11 (which basically means JavaScript version 11)) allows for using newer JavaScript syntax, and jest is for global variables used when writing tests.
-
parserOptions is specifically for allowing additional JavaScript language features. sourceType will prevent errors from import syntax, and ecmaFeatures allows for additional features outside the standard ecma syntax.
-
rules is where you can configure the linting behavior to your liking. Any that are prefixed with react/ are specific to the ESLint react plugin, similarly import/ would prefix any rules for the import plugin â we just donât need to add any here. Otherwise they are standard ESLint rules.
-
ignorePatterns lets you define specific files, folders, or patterns to exclude from linting. Both the node_modules and .next folders are actually excluded be default, and added here only as examples.
SoâŚthatâs a lot! But it will let us lint the Next.js project we have now with the --fix flag enabled to automatically format our code (next step!).
Add & Run the Lint Script
Now add two new scripts to your package.json file under the start script:
"start": "next start"**,
"lint": "eslint --ext .js ./",
"lint.fix": "eslint --fix --ext .js ./"**
^ Donât forget the , (comma) at the end of the start line! If you have integrated your IDE with ESLint youâll already have seen a bunch of errors if you open pages/index.js. The pages/api/hello.js should be error-free!
If you npm run lint now, you can also see all the errors in the console.
âŚnow do npm run lint.fix and youâll see a number of formatting changes to align the code with the linter rules, and no more errors!
Two Final Notes on Linting
-
Regarding IDE integration if you go that route â itâs super convenient to set it up to lint & fix whenever you save the file.
-
Assuming you use this base template in new projects, if you find yourself making updates to the .estlintrc file to accommodate your style preferences, remember to copy those back to the base project!
Jest & Testing Library: Install, Configure, Implement
Install Dependencies
Next up letâs add testing capabilities. Start with the install:
npm i -D jest @types/jest @testing-library/react @testing-library/jest-dom
^ jest for running the tests & @types/jest to help with IDE auto-complete when writing tests. @testing-library/react to render components in the testing environment & test them in a way that tries to mimic how users interact with them. @testing-library/jest-dom for additional DOM-related assertions.
Create Config Files
Create two new files at the project root for Jest: jest.config.js & jest.setup.js. Add this content to the jest.config.js file:
// Jest.config.js
module.exports = {
// Automatically clear mock calls and instances between every test
'clearMocks': true,
// The directory where Jest should output its coverage files
'coverageDirectory': '.coverage',
// A list of paths to modules that run some code to configure or set up the testing framework before each test
'setupFilesAfterEnv': ['./jest.setup.js']
}
^ There are a huge number of configuration options for Jest. This is a very small subset. clearMocks can prevent headaches with unintended persistence of mock data between tests. coverageDirectory is for generating test coverage, running jest with the --coverage flag. The most important piece here is setupFilesAfterEnv, which will run before each test file. Add this to the jest.setup.js file:
// Jest.setup.js
import '@testing-library/jest-dom'
^ This enables access to the additional assertions provided by the@testing-library/jest-dom package.
Create One More Config File đŹ
The last config file we need is called .babelrc (create it at the root of the project). We donât need to add any packages for Babel to run â itâs handled automagically by Next.js â but Jest needs this file to auto-detect how to transform the code before running tests. Add this to the file:
{
"presets": ["next/babel"]
}
Write a Test File
Create a file pages/index.test.js and add some test code:
import Home from './index'
import { render } from '@testing-library/react'
// `describe` is not required, but it helps the tests read nicely
describe('The Home Page Component', () => {
// Each test for the component will get an `it` block
it('should have exactly 1 `main` section', () => {
// The getByRole will error if there are less or more than 1 element found
const { getByRole } = render(<Home />)
const main = getByRole('main')
expect(main).toBeInTheDocument()
})
})
Add a Test Script
The last change for Jest is to the package.json file; Update it to add a test script under the lint script you added earlier:
"lint.fix": "eslint --fix --ext .js ./"**,**
**"test": "jest"**
Then in the project root in the terminal you can npm run test â and should see it passing!
Configuring Path Aliases/Absolute Imports
I have seen some debate that leads me to believe path aliases are a love-it or hate-it addition to a codebase. I personally hate having to remember which particular file Iâm working in and how many levels it is to import some other component or methodâŚso I love aliasing my import paths. The difference is:
// (Default) Relative imports đ:
import { Awesome } from '../../components/awesome
import { Method } from '../../../classes/method
// (Aliased) Absolute imports đ:
import { Awesome } from '@/components/awesome
import { Method } from '@/classes/method
^ Note that the syntax Iâm using, @/folder/path, is arbitrary â the @ may look fancy but it is only there to make it obvious that this isnât an npm package or a relative import â you could name the alias paths however you like!
The challenge setting these up is that once you start using them in your application and in your tests, all the different systems in your code that have to resolve imports (<-- good explanation of resolving modules â ignore the TypeScript parts đ ) need to understand these aliases. For us, that means adding configuration for Next.js, Jest, ESLint, and VSCode đ° ⌠so a lot of updates to the configuration weâve done thus far but donât worry âitâs not too drastic.
Create a Test Component
In order to verify the aliased paths are working we need something to import. Typically you would alias the top-level folders to reference the import path from there, but the only two top-level folders we have currently arenât really something we need to alias; Anything in pages/ probably shouldnât be imported anywhere else, and anything in public/ can already be referenced by absolute path in âsrcâ or âhrefâ attributes.
Instead, letâs create a new section in the code specifically for components. This will be two new folders and a file: components/callout/callout.js. Add this to the callout.js file:
import PropTypes from 'prop-types'
export default function Callout({ children }) {
return <p><strong style={{ color: 'red' }}>!</strong> {children} <strong style={{ color: 'red' }}>!</strong></p>
}
Callout.propTypes = {
children: PropTypes.node.isRequired
}
Try The Component
If you import that component in pages/index.js via a relative import, you can confirm itâs working:
**import Callout from '../components/callout/callout'**
import Head from 'next/head'
Then wrap the component around the âWelcomeâŚâ message in the h1 tag:
<h1 className="title">
**<Callout>Welcome to <a href="[https://nextjs.org](https://nextjs.org)">Next.js!</a></Callout>**
</h1>
Then npm run dev and see: âď¸ď¸ Welcome to Next.js! âď¸
Now change pages/index.js to use the aliased absolute import:
import Callout from '@/components/callout/callout'
âŚand you should see an error, yay! Letâs fix that!
Next.js & VSCode
Now that we have a component to test and we can see itâs not working, letâs start the configuration updates. Create a file in the project root named jsconfig.json. This will let us nab two birds with one stone since both VSCode and Next.js use this format for aliases. Add this to the file you just created:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/components/*": ["components/*"]
}
}
}
^ This wonât trigger a HRM refresh, so youâll have to stop the dev server and npm run dev again, but after that â your component should be up and running again!
In the IDE, if youâve integrated ESLint, youâll probably see an error still about how it is âUnable to resolve path to module,â so letâs update ESLint next.
ESLint
The configuration for ESLint will be added to .eslintrc, but first we need to install another package:
npm i -D eslint-import-resolver-alias
^ this package adds the functionality for ESLint to handle the resolution of aliased paths, which it canât do by default. Update the .eslintrc file by adding the following at the bottom, after the ignorePatterns property:
"ignorePatterns": ["node_modules/", ".next/"]**,**
**"settings": {
"import/resolver": {
"alias": [
["@/components", "./components"],
["@/classes", "./classes"]
]
}
}**
^ Iâve added an additional entry for a hypothetical /classes directory to show the syntax for multiple aliases. The need for each entry to be its own array was not intuitive for me.
If you npm run lint now, there shouldnât be any module import errors (you may have some spacing/minor issues from copy-pasting, so maybe npm run lint.fix), and the IDE error should have disappeared!
Jest
Finally we need to update Jest. In the file pages/index.test.js add an import for our Callout component:
**import Callout from '@/components/callout/callout'**
*import* Home *from* './index'
*import* { render } *from* '@testing-library/react'
...
⌠then try npm run test. You should see an error about the module:
Cannot find module â@/components/callout/calloutâ from âpages/index.test.jsâ
The addition to fix this will go into jest.config.js, a property called moduleNameMapper which uses RegEx syntax, so is a bit more complicated:
module.exports = {
// Automatically clear mock calls and instances between every test
clearMocks: true,
// The directory where Jest should output its coverage files
coverageDirectory: '.coverage',
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
moduleNameMapper: {
'^@/components(.*)$': '<rootDir>/components$1'
},
// A list of paths to modules that run some code to configure or set up the testing framework before each test
setupFilesAfterEnv: ['./jest.setup.js']
}
^ The regex is using a capturing group to take everything that comes after @/components and resolve it instead from the
âŚnow try npm run test, error should be gone!
Since we only added it for testing, you can remove the import Callout ... line we added to pages/index.test.js.
Important to remember
When you add new aliases in the future, youâll need to add them to three files:
-
jsconfig.json
-
.eslintrc
-
jest.config.js
Complete!
Whew, that was a lot đ° Fantastic job getting through it all, and you now have a robust Next.js base project you can use to build from in the future!
Questions? Comments?
Find me on twitter â @BenjaminWFox