Ben Fox
Next.js Setup | ESLint Jest React Testing Library and Absolute Imports
A comprehensive step-by-step guide to configuring Jest, React Testing Library, ESLint, and Absolute Path Aliases in a Next.js project.
Update: 03/08/22: Next.js 12 is virtually identical in terms of getting things set up. I've made a couple updates here including the use of the src
directory, and a few minor changes to the ESLint ruleset and the Jest/React Testing Library based on my updated preferences and new best practices. Also no more need for an explicit .babelrc
file!
Update: 06/28/21: Next.js 11 has some improvements when it comes to adding and using ESLint in a project, including some Next-specific linting rules. Some changes have been made to the original article based on the assumption that anyone reading new is going to be starting from Next.js v11+ rather than a previous version.
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) 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). Once the install completes, cd nextjs-base into your project folder.
Now, for better organization, create a new folder called src
and then move the pages
and styles
folders into src
. Your project should look like this:
.next/
node_modules/
public/
src/
- pages/
- api/
- styles/
.eslint.json
.gitignore
next.config.js
package-lock.json
package.json
README.md
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. You'll already have eslint
and eslint-config-next
- so let's add two more:
npm i -D eslint-plugin-react eslint-plugin-import
While thatâs running, open up the .eslintrc.json
file that is at the root of your site. Replace the contents with the configuration below.
Note that there are a ton of options for configuring ESLint.
You can just extend next
and next/core-web-vitals
if you want, leaving out the others. If you do, you can also omit the everything in the rules
property. Personally, I like the extra structure and what's there feel to me like a good default baseline. A number of the react/
specific rules are disabled to prevent conflicts with the default next-app
code style.
If you're working anyone else I'd highly recommend leaving the rules in place, it goes a long way towards keeping a codebase stylistically consistent:
{
"extends": [
"next",
"next/core-web-vitals",
"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"],
"func-style": 0,
"max-len": 0,
"no-magic-numbers": 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/prop-types": 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,
"react/jsx-newline": 0,
"react/jsx-props-no-spreading": 0,
"react/jsx-max-props-per-line": ["error", {"maximum": {"single": 3, "multi": 1}}]
},
"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 one new script to your package.json file under the start script:
"start": "next start",
"lint": "next lint",
"lint.fix": "next lint --fix"
^ Donât forget the , (comma) at the end of the "lint"
line! If you've integrated your IDE with ESLint youâll already have seen a bunch of errors if you open src/pages/index.js. The src/pages/api/hello.js should be error-free!
If you npm run lint
now, you can also see all the errors in the console. I've tweaked the eslint config over time, so the exact set of errors may be slightly different.
âŚ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
const nextJest = require('next/jest')
const createJestConfig = nextJest({
// Provide the path to your Next.js app to load next.config.js and .env files in your test environment
dir: './'
})
// Jest.config.js
const customConfig = {
// 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'],
// By default jest will use a node environment, so DOM elements (like document) will be undefined without this
'testEnvironment': 'jsdom'
}
module.exports = createJestConfig(customConfig)
^ 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.
Write a Test File
Create a file src/page-tests/index.test.js and add some test code:
import { render, screen } from '@testing-library/react'
import Home from '../pages/index'
// `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
render(<Home />)
const main = screen.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: src/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 src/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={styles.title}>
<Callout>Welcome to <a href="https://nextjs.org">Next.js!</a></Callout>
</h1>
Then npm run dev and see: âď¸ď¸ Welcome to Next.js! âď¸
Now change src/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/*": ["src/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.
Update 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", "./src/components"],
["@/classes", "./src/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!
Update Jest
Finally we need to update Jest. In the file src/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:
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:
const customConfig = {
// 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>/src/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'],
// By default jest will use a node environment, so DOM elements (like document) will be undefined without this
'testEnvironment': 'jsdom'
}
^ The regex is using a capturing group to take everything that comes after @/components and resolve it instead from the /components specified on the right. Check it out on regex101.com for a more complete breakdown of whatâs going on.
âŚ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 src/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