Automatically lint & format your code on commit when using Next.js

In this post, we will be setting up Next.js with Husky and lint-staged to run ESLint and Prettier whenever we commit a file.

If you want to take a look at the final product first, or if you don't care about the step-by-step, here's the accompanying repo.

You probably already know, or can imagine, the advantages of automatically linting and formatting each and every piece of code commited to your repo. So I won't convince you of its merits here, but get straight into the action.

Set up a new Next.js Project

Start by setting up a new Next.js project if you don't have one yet:

npx create-next-app --typescript
# or
yarn create next-app --typescript

Here, we are using the Typescript template. But you can simply leave the --typescript off if you like to suffer. Your choice, really.

Choose whatever name you want. I'll presume you chose "website" though.

If you want to read more about the process of setting up Next.js, take a look at their getting started guide.

Install Prettier as a devDependency

ESLint will look at your code and try to prevent potential bugs by looking at its semantics. Prettier will look at the formatting and change it according to its rules. Since Next.js comes with ESLint pre-installed and pre-configured, that's already taken care of. That leaves Prettier.

Change into your newly created project folder:

cd website

Then, install Prettier as a devDependency:

npm install --save-dev --save-exact prettier
# or
yarn add --dev --exact prettier

Create an empty Prettier config:

echo {}> .prettierrc.json

This will let tools like your editor know you are using Prettier.

Optional: Create a .prettierignore file

If you plan to use Prettier outside of the Git hook we are setting up below, you should probably create a .prettierignore file. This allows you to list folders and files you don't want to format.

touch .prettierignore

The Prettier documentation says it's a good idea to base this on your "gitignore and .eslintignore (if you have one)." So ... do that.

If you want to read more about the process of setting up Prettier, take a look at their installation guide.

Edit your .eslintrc

Some of the ESLint rules Next.js comes pre-configured with are about formatting. But we want Prettier to handle everything related to the formatting of our code. This is why we'll install eslint-config-prettier and add it to our .eslintrc, where it will disable all existing rules that might interfere with Prettier.

Install eslint-config-prettier by running the following:

npm install --save-dev eslint-config-prettier
# or
yarn add --dev eslint-config-prettier

The .eslintrc Next.js created should look like the following:

// In file .eslintrc
{
  "extends": ["next", "next/core-web-vitals"]
}

Change this to the following:

// In file .eslintrc
{
  "extends": ["next", "next/core-web-vitals", "prettier"]
}

If you want to read more about the process of combining ESLint and Prettier in a Next.js project, take a look at the Next.js "Usage with Prettier" guide, as well as Prettier's "Integrating with Linters" guide.

Install lint-staged

At this point, you would be able to run ESLint and Prettier manually. But ain't nobody got time for that. Also, "Manually Lint & Format your Code on Commit When Using Next.js" is not the title of this article and I don't want to disappoint you. So let's introduce lint-staged into the mix.

The good thing is that they make it very easy to set up:

npx mrm@2 lint-staged

This will install Husky, a tool that makes is easy to manage Git hooks, and detect which linting and formatting tools are already installed. It will then set everything up more or less correctly. We'll come to the "less" part next.

If you want to read more about the process of setting up lint-staged, take a look at their installation and setup guide, as well as Prettier's pre-commit hook guide.

Edit the Git Hook

After running the above, you should have the following entry in your package.json:

// In file package.json

// ...
"lint-staged": {
  "*.js": "eslint --cache --fix",
  "*.{js,css,md}": "prettier --write"
}

Change this to the following:

// In file package.json

// ...
"lint-staged": {
  "*.{js,jsx,ts,tsx,css,md}": "prettier --write"
}

This will run Prettier on all staged files of the listed types when you run git commit.

Now, you would be forgiven to assume that to also run ESLint, we should put it in there as well. But since Next.js comes with its own pre-configured wrapper around ESLint, wich doesn't take the files it operates on as arguments, we will do something slightly different. We will edit the Git hook that Husky created, during the installation of lint-staged, directly. Right now, it should look like this:

# In file .husky/pre-commit

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

npx lint-staged

Change this to the following:

# In file .husky/pre-commit

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

yarn lint:write && npx lint-staged

And then add the following script to your package.json:

// In file package.json

// ...
"scripts": {
  // ...
  "lint:write": "next lint --cache --fix",
},

This will call the Next.js version of ESLint and tell it to automatically --fix any issues that it finds that can be fixed automatically. Also, the --cache tells it to only do so on changed files.

If you want to read more about the undocumented command line options Next.js' lint command accepts, take a look at my post on the topic.

We're Done!

Now, when you run git commit, both ESLint and Prettier should check that you don't commit any crap. This should also work when using VSCode's own Git UI. Other Git UIs might have issues though. Sublime Merge for example can't find my node.js installation, most likely because it's installed and managed via nvm. There are almost certainly solutions to this, but since I haven't looked them up yet they fall outside the scope of this tutorial.

Don't forget that you can use or take a look at the accompanying repo.

Hope you found this helpful. If you find any erros or run into issues, feel free to tell me in the comments.

💡 This post was published 2021/07/14 and last updated 2021/07/14. Since node libraries change and break all the time, there's a non-trivial chance that all of this is obsolete and none of it works when you read this in your flying taxi, gulpin' on insect protein shakes sometime in the distant future or, let's be honest here, next week. If so, tell me in the comments and I'll see if an update would still be relevant.

27