Software Engineer
Set up ESLint, Prettier, Husky and lint-staged with Next.js
Have you ever pushed code with formatting issues, linting errors, or even TypeScript errors? We've all been there. These small oversights can lead to failed CI/CD pipelines, messy pull requests, and unnecessary back-and-forth with reviewers.
In this guide, I'll show you how to set up a robust pre-commit workflow that automatically checks and fixes your code before each commit. We'll use:
- ESLint - To catch potential bugs and enforce coding standards
- Prettier - To ensure consistent code formatting
- Husky - To set up Git hooks that run automatically
- lint-staged - To run checks only on the files you're committing
- TypeScript - To verify type safety
By the end, you'll have a seamless workflow that ensures your code is clean, consistent, and error-free before it ever reaches your repository.
Why Use Pre-commit Hooks?
Pre-commit hooks provide several benefits:
- Catch errors early - Find and fix issues before they're committed
- Maintain code quality - Enforce consistent style and best practices
- Save time - Avoid back-and-forth code review comments about formatting
- Improve team collaboration - Everyone's code follows the same standards
Let's get started!
Installation
First, we need to install all the necessary packages. You can install them individually or all at once:
# Install packages individually
# Install ESLint
npm install -D eslint eslint-config-next
# Install TypeScript checking tools
npm install -D tsc-files
# Install Prettier with plugins for Tailwind and import sorting
npm install -D prettier prettier-plugin-tailwindcss @ianvs/prettier-plugin-sort-imports
# Install Husky and lint-staged
npm install -D husky lint-staged
Or install everything at once:
npm install -D eslint eslint-config-next prettier prettier-plugin-tailwindcss @ianvs/prettier-plugin-sort-imports husky lint-staged tsc-files
Configure Prettier
Prettier handles code formatting. Let's create a configuration file that works well with Next.js and Tailwind CSS projects.
Create a .prettierrc.json
file in your project root:
{
"trailingComma": "es5",
"tabWidth": 2,
"semi": false,
"bracketSpacing": true,
"useTabs": false,
"printWidth": 80,
"singleQuote": true,
"importOrder": [
"<TYPES>^(node:)",
"<TYPES>",
"^@/types/(.*)$",
"<TYPES>^[.]",
"",
"^(react/(.*)$)|^(react$)",
"^(next/(.*)$)|^(next$)",
"<THIRD_PARTY_MODULES>",
"",
"^@/components/ui/(.*)$",
"^@/components/(.*)$",
"^@/app/(.*)$",
"^[./]",
"",
"^@/lib/hooks/(.*)$",
"^@/lib/(.*)$",
"^(@/utils/(.*)|^(@/utils$))$",
"^@/config/(.*)$",
"",
"^@/styles/(.*)$",
"./globals.css"
],
"importOrderParserPlugins": ["typescript", "jsx", "decorators-legacy"],
"plugins": ["prettier-plugin-tailwindcss", "@ianvs/prettier-plugin-sort-imports"]
}
This configuration:
- Uses 2 spaces for indentation
- Omits semicolons (personal preference)
- Limits line length to 80 characters
- Uses single quotes
- Organizes imports in a logical order (types first, then React/Next.js, then third-party modules, etc.)
- Integrates with Tailwind CSS for class sorting
Next, create a .prettierignore
file to exclude certain files and directories from formatting:
.next
.cache
.velite
package-lock.json
public
node_modules
next-env.d.ts
next.config.ts
yarn.lock
Configure ESLint
ESLint helps catch potential bugs and enforce coding standards. Next.js comes with a built-in ESLint configuration, but we can extend it with our own rules.
Create an .eslintrc.json
file in your project root:
{
"extends": ["next/core-web-vitals"],
"plugins": [],
"rules": {
"no-unused-vars": "warn", // Change from error to warning for unused variables
"no-empty-pattern": "warn" // Change from error to warning for empty destructuring patterns
}
}
This configuration extends Next.js's recommended rules and adjusts a couple of rules to be warnings instead of errors. You can customize this further based on your team's preferences.
Set Up Husky
Husky allows us to set up Git hooks. Let's initialize it:
npx husky init
This creates a .husky
directory with a sample pre-commit hook. Now, let's configure our pre-commit hook to run lint-staged:
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
echo '🚀 Committing the staged changes...'
npm run lint-staged
echo '✅ Committing changes.'
This script runs our lint-staged command before each commit and provides some friendly console output.
Configure lint-staged
lint-staged allows us to run commands only on the files that are staged for commit, which makes the process much faster.
Create a .lintstagedrc.mjs
file in your project root:
export default {
// Run type-checking on TypeScript files
"**/*.{ts,tsx}": "tsc-files --noEmit --skipLibCheck",
// Run ESLint on JavaScript and TypeScript files
"**/*.{js,jsx,ts,tsx}": "eslint --fix",
// Format various file types with Prettier
"**/*.{js,jsx,ts,tsx,json,css,scss,md}": "prettier --write"
};
This configuration:
- Runs TypeScript type checking on
.ts
and.tsx
files - Runs ESLint with auto-fix on JavaScript and TypeScript files
- Formats all supported files with Prettier
Update package.json
Finally, add the necessary scripts to your package.json
:
{
"scripts": {
// ... your existing scripts
"format": "prettier --write .",
"lint": "eslint . --fix",
"typecheck": "tsc --noEmit --skipLibCheck",
"lint-staged": "lint-staged",
"prepare": "husky"
}
}
Testing Your Setup
Let's make sure everything is working correctly:
- Make some changes to your code
- Stage the changes with
git add .
- Try to commit with
git commit -m "Test pre-commit hooks"
You should see the pre-commit hook run, checking your types, linting your code, and formatting it before the commit completes.
Conclusion
Congratulations! You've set up a robust pre-commit workflow that ensures your code is clean, consistent, and error-free before it's committed. This setup will save you time, reduce errors, and make your codebase more maintainable.
By automating these checks, you can focus on what matters most: building great features for your Next.js application.
Happy coding!