Here, Take this Configuration
I get it. If you're just here to find a good, working ESLint configuration for a NextJS project, then look no further. Copy what's below. Although, it's probably out of date, so you can find a version that's been updated since I published this post in my open source project Historio ➡️ in Github here ⬅️.
eslint.config.mjs
Run this command to install necessary npm packages:
npm i --save eslint typescript-eslint eslint-config-next eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-tailwindcss eslint-plugin-unicorn
Why Lint
When starting a new project, the very first thing I do is write some really sloppy, hacky, and messy code that (mostly) just works. I'm trying to validate an idea; to get a product off the ground.
This lasts all of about 2 pull requests before it gets out of hand, and I need some rules. You can see the exact moment in Historio where I realized inconsistent code was holding me back in this pull request where linting was first applied.
Having conventions on shared projects helps keep everyone aligned and working in the same direction. Conventions allow us to focus code reviews and discussion on the logic problems that matter instead of the semicolon placement or tab size that - frankly - only matter because they’re a distraction. Written conventions for a team, starting with a style guide but encompassing any rules that keep engineers focused on engineering, are thus essential.
The benefits to a team may be obvious, but even individual projects deserve conventions. Perhaps even stricter ones. Indeed, when I’m working on a project alone, I tend to codify a larger set of conventions than I would with a team simply because I get to do everything exactly my way. It's unclear if this is software engineering bliss or OCD. Every convention eliminates decisions I would otherwise have to make in the future (often many times over). Further, without a colleague to review every line of code I write, conventions help keep me consistent and tidy even when I really want to move fast.
How I Arrived at this Configuration
Other coders have thought longer and harder about conventions than I ever want to. When spinning up a new linting configuration or style guide, I look to what already exists. For my stack, this includes the following ESLint Plugins. For each, I start with their recommended config:
- Vercel's rules for NextJS
- Official React eslint plugin
- Typescript ESLint
- Tailwind
- Drizzle
- Unicorn - This is the big one, with lots of rules and opinions on general typescript styling outside of conventions for the specific frameworks covered by the plugins above.
Additionally, here are a few plugins I evaluated but decided not to use:
AirBbB. I usually start here, but Vercel's config covered most of what I care about for both React and Node code styling.
XO. This feels like the Black of Typescript linting. XO has styles for everything and is very opinionated. This can be nice, because it takes a lot of code style decisions off of your plate. But beware it can be cumbersome to implement in the middle of a project because it will require extensive reformatting. In lieu of XO, I found Unicorn opinionated enough and more immediately useful.
Technical Note on ESLint Config
There are too many ways to configure ESLint. I started with the .eslintrcs.json
config that came from create-next-app
. However, this configuration is now deprecated in favor of eslint.config.mjs
.
Because I couldn't easily extend this deprecated configuration, I (swore and then) scrapped it and initialized a new eslint config with npm init @eslint/config@latest
. I then added the configuration for next
wrapped with eslint's flat compat utility. Here's the snippet in eslint.config.mjs
:
import { FlatCompat } from "@eslint/eslintrc"
export default const config = [
// other ESLint plugins and rulesets
...compat.config({
extends: ["next"],
settings: {
next: {
rootDir: ".",
},
},
}),
]
Most other plugins offer a configuration that can be plugged into eslint.config.mjs
directly. But if you see an error starting with:
Support for loading ES Module in require() is an experimental feature and might change at any time
then you probably need to wrap a plugin with that flat config migration utility
Sharpen Skills by Exploring Linting Config
Ultimately, there is no single "right" style guide or linting configuration. You should choose what works best for your project. When creating or revisiting my configurations, I often uncover new tricks or coding conventions that make me a better software engineer. when putting together this configuration, I learned:
It's important to deal with the ambiguity of
null
andundefined
in JS/TS, but how you do it is project-dependent. By default, unicorn encourages use ofundefined
overnull
always, but there are very legitimate reasons to usenull
, like if you have a lot of code that leverages an ORM withnull
values. This long thread has many great points and no single right answer.Avoid passing a function reference directly to iterators (i.e.
{elements.map(callback)}
) details
Your style guide also shouldn't be static. It should evolve as your project grows. Not only do the needs of your project change, but conventions, libraries, and coding languages evolve around your work, too. It's okay (good, even) to make considered and reasoned changes to your conventions, even if you're backtracking on something you once felt was really important.
Top comments (1)
What do you include in your Next.JS linting that I missed?