Complete Guide on Setting Up ESLint & Prettier to Work With Next.js-Typescript Project (create-next-app --ts)

I started a new project with Next.js and wanted to get eslint and prettier to work with it, but the overall process turned out to be harder than I anticipated. So I decided to document the steps that I followed to help out the next person facing the same issue.

Issue

create-next-app doesn't come with eslint set up out-of-the-box to run automatically, so you need to set it up manually. Since next encapsulates all the logic for running the dev server, we need to make some modifications to the next.config.js file to get eslint to run while the dev server is running. For anyone just starting out with Next.js it can be an overwhelming experience given how next is slightly different from a basic create-react-app. As part of this guide we'll also be creating a pre-commit hook using husky & lint-staged for running eslint before we commit our code.

Creating a Next.js app (create-next-app)

Let's start by creating a new application. If you've already created one then feel free to skip this step.

It's pretty easy to get going. Just run the command posted on the Next.js getting started page.

npx create-next-app --ts

Note that here we're using the --ts option to get started with a typescript project.

Once this is complete, you should have an .eslintrc.json file along with a lint script in your package.json which you can execute by running npm run lint.

Installing ESLint plugins & Prettier

Now let's install some plugins for eslint that we'll use to get eslint working nicely with typescript & prettier.

Install typescript-eslint/eslint-plugin

We'll speify this plugin in our .eslintrc.json file

npm install --save-dev @typescript-eslint/eslint-plugin

Let's update the .eslintrc.json file and add this plugin.

// .eslintrc.json

"plugins": ["@typescript-eslint"], // Add this line
"extends": "next/core-web-vitals"

Adding Prettier

Let's add prettier to our project and define some basic rule in .prettierrc file.

npm install --save-dev prettier

Now we'll create a basic .prettierrc file

// .prettierrc

{
  "semi": false,
  "trailingComma": "es5",
  "singleQuote": true,
  "tabWidth": 2,
  "useTabs": false
}

Feel free to make any modifications based on your preference. Also, at this point you should set up your IDE/TextEditor to work with prettier and automatically format the document on save. This will make development much faster and make your life 1000 times better.

VSCode + Prettier

Make sure prettier is working and formatting your document as expected before proceeding. You might have to restart your IDE/TextEditor for the changes to take effect

ESLint With Prettier

Let's setup eslint to work with prettier and show prettier errors when linting.

npm install --save-dev eslint-config-prettier

Now let's add some extensions to our .eslintrc.json file

// .eslintrc.json

{
  "plugins": ["@typescript-eslint"],
  "extends": [
    "next",
    "next/core-web-vitals",
    "plugin:@typescript-eslint/recommended",
    "prettier" // Add "prettier" last. This will turn off eslint rules conflicting with prettier. This is not what will format our code.
  ]
}

Let's make sure what we have so far is working. Let's define an unused variable in the index.tsx file const errorCausingVar = 'string' and then run npm run lint in your terminal. If everything works then you should see a warning that looks something like this

./pages/index.tsx
6:7  Warning: 'errorCausingVar' is assigned a value but never used.  @typescript-eslint/no-unused-vars

Running Lint on Dev Server

Now we need to make sure that the next dev script runs the linter so that we can see the issues in terminal as and when you make changes to the file with the dev server running. You might see online that some places recommend using eslint-loader with webpack but since that library has been deprecated, we'll use the new eslint-webpack-plugin library instead.

Installing eslint-webpack-plugin

Let's install the plugin to use with webpack

npm install --save-dev eslint-webpack-plugin

Adding Plugin to Webpack

Now we'll update the webpack config using the next.config.js file. For more details on how this file works you should checkout Next.js official documentation

// next.config.js

/* eslint-disable */
const ESLintPlugin = require('eslint-webpack-plugin')

/** @type {import('next').NextConfig} */
module.exports = {
  reactStrictMode: true,
  webpack: (config, { dev }) => {
    if (dev) { // Add this plugin only in dev mode
      config.plugins.push(
        new ESLintPlugin({
          context: './', // Location where it will scan all the files
          extensions: ['js', 'jsx', 'ts', 'tsx'], // File formats that should be scanned
          exclude: ['node_modules', 'dist'], // Exclude everything in these folders
        })
      )
    }
    return config
  },
}

Here we're adding a new instance of the eslint-webpack-plugin plugin to the webpack config and setting it up to scan all the .js, .ts, .jsx, and .tsx files except for those inside node_modules and dist folders.

Now let's make sure everything is working. Leave the unused variable that we added earlier in index.js and run the dev server npm run dev. Now go to the browser and hit localhost:3000 (the URL of your dev server) and then check the terminal, you should see a warning about the unused variable

/pages/index.tsx
  6:7  warning  'errorCausingVar' is assigned a value but never used  @typescript-eslint/no-unused-vars

At this point you can start modifying .eslintrc.json as per your preference and it should work as expected. I like to add the following rules to mine

// .eslintrc.json

{
  "plugins": ["@typescript-eslint"],
  "extends": [
    "next",
    "next/core-web-vitals",
    "plugin:@typescript-eslint/recommended",
    "prettier" // Add "prettier" last. This will turn off eslint rules conflicting with prettier. This is not what will format our code.
  ],
  "rules": { // Rules that I add manually.
    "@typescript-eslint/no-unused-vars": "error",
    "@typescript-eslint/no-explicit-any": "error"
  }
}

Now that we have everything working, the last step would be to add a hook which will run the linter on git commit to ensure that we don't end up pushing lint errors to our repository.

Adding husky & lint-staged to check for errors on git commit

Even though we've added lint check to our dev server, let's add another layer of check to ensure that we don't end up pushing lint errors to our remote repository.

Adding husky

Husky is a tool that allows us to create hooks which can run what we tell it on a git event. We're going to use the pre-commit hook and run our linter.

npm install --save-dev husky

Now let's install husky's hooks.

npx husky install

Now let's add a pre-commit hook that will run lint-staged (we'll install set it up in the next step)

npx husky add .husky/pre-commit "npx lint-staged"

You show see this in your terminal

husky - created .husky/pre-commit

And you should see a per-commit file that looks like this in .husky folder

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx lint-staged

Adding lint-staged

Lint staged is a tool that will run eslint on staged files. This will speed up the commit process by reducing the files that needs to be checked on every commit.

npm install --save-dev lint-staged

Now let's configure lint-staged in our package.json

{
  /*
  ... rest of the package.json
  */

  // Add the following lines
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": [
      "eslint --fix"
    ]
  }
}

Here we're telling lint-staged to run eslint --fix on the following file types .js, .ts, .jsx, and .tsx.

Now, with the unused variable still in index.js, let's try to commit our changes

git add --all

git commit -m "Test commit to see if husky & lint-staged works"

Now your should see something like this in your terminal

✔ Preparing...
⚠ Running tasks...
  ❯ Running tasks for *.{js,jsx,ts,tsx}
    ✖ eslint --fix [FAILED]
↓ Skipped because of errors from tasks. [SKIPPED]
✔ Reverting to original state because of errors...
✔ Cleaning up...

Followed by

/pages/index.tsx
  6:7  error  'errorCausingVar' is assigned a value but never used  @typescript-eslint/no-unused-vars

✖ 1 problem (1 error, 0 warnings)

husky - pre-commit hook exited with code 1 (error)

Note: You can skip this check on commit by adding --no-verify to your git commit command. Eg. git commit -m "skip lint check" --no-verify

Voila! We have completed setting up eslint to work with our Next.js project! 🎉

Here's a boilerplate that I created with eslint & prettier (as described above), docker, and nginx set up with a basic create-next-app. Feel free to check it out and provide feedback! :)