It's important to have clean code. Your team reads code a lot more than they write new code. Hidden bugs and inconsistent formatting make things take longer for everyone. ESLint finds mistakes before they go live. Prettier automatically formats your code. They work together to turn messy codebases into projects that are easy to keep up with.
This guide shows you how to set up both tools in projects that use React and Next.js. You will learn the best ways for teams to work together, how to set up systems, and how to integrate them.
Why Code Quality Tools Are Important for Modern Development
Each developer has their own way of doing things. Some people like single quotes. Some people use double quotes. Some people put semicolons everywhere. Some people don't even bother with them. Pull requests turn into formatting arguments instead of code reviews when there aren't any shared standards.
ESLint checks your JavaScript and TypeScript code for problems that might happen. The tool finds variables that aren't being used, code that can't be reached, and patterns that are likely to cause bugs. Prettier takes care of the formatting. The tool changes the style of the code you submit so that it is always the same.
Using both tools at the same time has two benefits. ESLint finds logical mistakes and makes sure that coding patterns are followed. Prettier makes it so that there are no style arguments by formatting everything the same way.
These tools help professional development teams keep the code quality high on big projects. The time spent setting things up pays off in the form of faster code reviews and fewer bugs in production.
Prerequisites
Check that your development environment meets these criteria before you start:
- Node.js version 18 or higher
- npm or yarn installed
- A React or Next.js project already set up
If you don't have a project, make a new one with the command:
npx create-next-app@latest my-project --typescript
Installing the Necessary Packages
It takes a few packages working together to set up. To save time, install everything with one command. Every package has a certain use:
- @typescript-eslint packages – make TypeScript work
- eslint-config-airbnb – has a set of rules that a lot of businesses use
- eslint-config-prettier – stops ESLint and Prettier from getting in each other's way
Using npm:
npm install --save-dev eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-config-airbnb eslint-config-prettier eslint-import-resolver-typescript eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-prettier eslint-plugin-react eslint-plugin-react-hooks prettier @trivago/prettier-plugin-sort-imports
Using yarn:
yarn add -D eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-config-airbnb eslint-config-prettier eslint-import-resolver-typescript eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-prettier eslint-plugin-react eslint-plugin-react-hooks prettier @trivago/prettier-plugin-sort-imports
Setting Up ESLint
In the root of your project, make a file called .eslintrc. This file tells ESLint what rules to follow. The extends array loads rules from other configurations. Order is important here. To turn off formatting rules that don't work with other configs, the prettier config must come after them.
{
"root": true,
"env": {
"browser": true,
"es2021": true,
"node": true
},
"extends": [
"airbnb",
"airbnb/hooks",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:jsx-a11y/recommended",
"prettier"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["@typescript-eslint", "react", "react-hooks", "jsx-a11y", "prettier"],
"settings": {
"react": {
"version": "detect"
},
"import/resolver": {
"typescript": {
"alwaysTryTypes": true
}
}
},
"rules": {
"react/react-in-jsx-scope": "off",
"react/prop-types": "off",
"react/jsx-filename-extension": ["warn", { "extensions": [".tsx", ".jsx"] }],
"import/extensions": [
"error",
"ignorePackages",
{
"ts": "never",
"tsx": "never",
"js": "never",
"jsx": "never"
}
],
"no-restricted-imports": [
"error",
{
"patterns": ["../"]
}
],
"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"] }
],
"@typescript-eslint/no-unused-vars": "error",
"prettier/prettier": "error"
}
}
Understanding the Most Important ESLint Rules
The react/react-in-jsx-scope rule is turned off because you don't have to import React in every file starting with React 17. The react/prop-types rule is turned off because TypeScript does a better job of checking types than PropTypes.
The no-restricted-imports rule says not to use relative imports like ../. This makes it easier to use path aliases to make import statements cleaner. The padding-line-between-statements rule says that there must be blank lines before return statements. This makes the code easier to read by separating the return from the rest of the logic.
Setting Up Prettier
Make a file called .prettierrc in the root of your project. Each option changes how formatting works in a certain way.
{
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"useTabs": false,
"printWidth": 100,
"trailingComma": "es5",
"bracketSpacing": true,
"jsxSingleQuote": false,
"arrowParens": "always",
"endOfLine": "lf",
"plugins": ["@trivago/prettier-plugin-sort-imports"],
"importOrder": ["^react", "<THIRD_PARTY_MODULES>", "^@/(.*)$", "^[./]"],
"importOrderSeparation": true,
"importOrderSortSpecifiers": true
}
- printWidth – tells Prettier how long a line can be before it wraps code
- singleQuote – makes all of your code use single quotes instead of double quotes
- trailingComma – puts commas after the last item in arrays and objects (makes git diffs cleaner when you add new items)
Sorting Imports Automatically
The Trivago plugin takes care of organising imports on its own. The order of the sorting is set by the importOrder array:
- React imports come first
- Third-party packages come next
- Internal
@/alias imports follow - Relative imports come last
The importOrderSeparation option puts empty lines between each group of imports. This visual separation makes it easier to scan imports.
Setting Up Path Aliases
Path aliases make imports easier and stop relative paths from getting messy. You write @/components/Button instead of ../../../components/Button.
Add path mappings to your tsconfig.json file:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}
Path aliases are very important for teams working on complex projects to keep their codebases readable as the project grows.
Adding Stylelint for SCSS and CSS
Linting JavaScript checks your components. You also need to pay attention to your stylesheets. Stylelint finds mistakes in CSS and makes sure that styles are always the same.
Install the following packages:
npm install --save-dev stylelint stylelint-config-standard-scss stylelint-config-prettier-scss stylelint-scss
Make a file called .stylelintrc.json in the root of your project:
{
"extends": ["stylelint-config-standard-scss", "stylelint-config-prettier-scss"],
"plugins": ["stylelint-scss"],
"rules": {
"at-rule-no-unknown": null,
"scss/at-rule-no-unknown": true,
"selector-class-pattern": null
}
}
The configuration turns off the standard at-rule-no-unknown rule because SCSS has its own @-rules, such as @mixin and @include. The SCSS version of the rule handles these correctly.
Setting Up Scripts in package.json
Add scripts to your package.json file so that linters can run:
{
"scripts": {
"lint": "prettier --check . && eslint . && stylelint '**/*.scss'",
"lint:fix": "eslint . --fix && prettier --write . && stylelint '**/*.scss' --fix",
"format": "prettier --write .",
"format:check": "prettier --check ."
}
}
- lint – checks everything but doesn't change anything (use this in CI/CD pipelines to stop builds that have formatting problems)
- lint:fix – fixes things that the tools find wrong on their own
Setting Up Git Hooks with Husky
Discipline is needed to run linters by hand. People who make things forget. Bugs get through. Git hooks make the process easier by running checks before each commit.
Install Husky and lint-staged:
yarn add -D husky lint-staged
yarn dlx husky init
Put the lint-staged settings in your package.json file:
{
"lint-staged": {
"*.{ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{js,jsx}": ["eslint --fix", "prettier --write"],
"*.scss": ["stylelint --fix", "prettier --write"],
"*.{json,md}": ["prettier --write"]
}
}
Create the pre-commit hook in .husky/pre-commit:
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged
The Commit Workflow
Every time you commit, the workflow runs on its own:
- The developer makes changes and stages them
- The developer runs
git commit - Husky starts the pre-commit hook
- lint-staged only runs linters on files that are staged
- If all checks pass, the commit works
- If any checks fail, the commit stops and shows error messages
Integration with VS Code
Your editor should underline mistakes as you type. You don't have to run terminal commands to get real-time feedback from VS Code extensions.
Install these extensions from the VS Code marketplace:
- ESLint
- Prettier
- Stylelint
In your project, make a file called .vscode/settings.json:
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.fixAll.stylelint": "explicit"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"],
"stylelint.validate": ["css", "scss"]
}
- editor.formatOnSave – makes Prettier run every time you save a file
- editor.codeActionsOnSave – automatically runs ESLint and Stylelint fixes
Understanding the Rules for Spacing Code
Consistent spacing makes things easier to read. There are rules in the ESLint configuration that require blank lines between logical code sections. Code that isn't spaced out properly looks cramped. Variables, function calls, and control statements all mix together.
It's easier to keep up with projects that are formatted correctly. When you use Material-UI components, consistent linting finds common prop mistakes and accessibility problems before they go live.
Making Ignore Files
Not all files need to be linted. Create these ignore files:
.eslintignore:
node_modules/
.next/
out/
build/
dist/
public/
.prettierignore:
node_modules/
.next/
out/
build/
dist/
public/
pnpm-lock.yaml
package-lock.json
yarn.lock
.stylelintignore:
node_modules/
.next/
out/
build/
dist/
public/
These ignore files prevent linters from wasting time on created files, dependencies, and build outputs.
Common Mistakes and How to Fix Them
Can't Find Path to Module
This error happens when ESLint can't figure out what to do with @/ imports. To correct the problem, you need to install and set up the TypeScript resolver:
npm install --save-dev eslint-import-resolver-typescript
Ensure that your tsconfig.json file has the path mapping and that your ESLint settings have the import resolver configuration:
{
"settings": {
"import/resolver": {
"typescript": {
"alwaysTryTypes": true
}
}
}
}
Prettier and ESLint Conflicting Rules
If both tools flag the same line but offer you different suggestions, look at your extends array. To override rules from other configs that are in conflict, the prettier config must come last.
Husky Hooks Are Not Working
If commits go through without running linters, reinstall Husky and verify that the pre-commit file is there and has the right script content.
Fixing Your Configuration
If your rules don't work the way you expect them to, run:
npx eslint --print-config src/components/Button.tsx
This shows how the merged configuration works on a certain file. The output shows which rules are in effect and where they came from.
Improving Performance
Linting takes longer on big projects with thousands of files. To skip files that haven't changed, add caching to your lint script:
{
"scripts": {
"lint": "eslint . --cache --cache-location node_modules/.cache/eslint"
}
}
The lint-staged tool only works on files that are staged. This method is faster than checking the entire codebase for errors on every commit.
Sharing Settings Between Projects
If your agency works on more than one project, sharing ESLint and Prettier settings can save you time when setting things up. Make a shared configuration package that has your rules in it. Include the shared config in each project and utilise it in your extends array.
This method ensures uniform linting across all your projects and centralises rule updates. If you improve a rule in the shared package, it automatically updates all the projects that use it.
When teams build apps that support internationalisation, they benefit from having the same linting rules for translation files and component code.
Summary
ESLint and Prettier turn messy codebases into projects that are easy to work on. ESLint finds bugs and makes sure that patterns are followed. Prettier takes care of formatting on its own. Your team can keep the same level of quality without having to do any extra work with Stylelint for CSS and Husky for Git hooks.
This guide's configuration will get you a setup that is ready for production:
- Path aliases make it easier to import
- Automatic import sorting keeps files organised
- Git hooks stop badly formatted code from getting into your repository
- VS Code integration gives you feedback right away when you type
Start by utilising the provided configuration files. Change the rules to fit what your team wants. The goal is to have code that is easy to read and understand throughout your whole codebase.








