Muhammad Tehseen Khan

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:

terminal
# 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:

terminal
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:

.prettierrc.json
{
  "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:

.prettierignore
.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:

.eslintrc.json
{
  "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:

terminal
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:

.husky/pre-commit
#!/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:

.lintstagedrc.mjs
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:

  1. Runs TypeScript type checking on .ts and .tsx files
  2. Runs ESLint with auto-fix on JavaScript and TypeScript files
  3. Formats all supported files with Prettier

Update package.json

Finally, add the necessary scripts to your package.json:

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:

  1. Make some changes to your code
  2. Stage the changes with git add .
  3. 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!