Tekmium.
May 17, 2025

How to Set Up ESLint + Prettier + Husky + Lint-Staged in a Next.js Project

Go back
How to Set Up ESLint + Prettier + Husky + Lint-Staged in a Next.js Project

🔧 How to Set Up ESLint + Prettier + Husky + Lint-Staged in a Next.js Project

In this post, we’ll walk through setting up a powerful developer workflow using ESLint, Prettier, Husky, and Lint-Staged in a modern Next.js + TypeScript project.

✅ Goal: Ensure clean, consistent code formatting, automatic linting before commit, and enforceable team-wide coding standards.

Install Dependencies

Install the necessary packages if you’re using TypeScript and React:

npm install -D @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-simple-import-sort eslint-config-prettier

Prettier Configuration

📄 .prettierrc

{
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "all",
  "printWidth": 100,
  "bracketSpacing": true,
  "arrowParens": "always",
  "endOfLine": "lf",
  "overrides": [
    {
      "files": "*.json",
      "options": {
        "printWidth": 80
      }
    }
  ]
}

📄 .prettierignore

node_modules/
.next/
dist/
public/
coverage/
prisma/migrations/

ESLint with Flat Config

📄 eslint.config.js

import path from 'node:path';
import { fileURLToPath } from 'node:url';
import js from '@eslint/js';
import typescriptEslint from '@typescript-eslint/eslint-plugin';
import typescriptParser from '@typescript-eslint/parser';
import simpleImportSort from 'eslint-plugin-simple-import-sort';
import { FlatCompat } from '@eslint/eslintrc';
import { fixupConfigRules, fixupPluginRules } from '@eslint/compat';
 
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
  baseDirectory: __dirname,
  recommendedConfig: js.configs.recommended,
  allConfig: js.configs.all,
});
 
export default [
  { ignores: ['node_modules/**', '.next/**', 'dist/**', 'public/**'] },
  ...fixupConfigRules(
    compat.extends(
      'eslint:recommended',
      'plugin:import/recommended',
      'plugin:import/typescript',
      'plugin:@typescript-eslint/recommended',
      'plugin:react/recommended',
      'plugin:react-hooks/recommended',
      'plugin:jsx-a11y/recommended',
      'plugin:@next/next/recommended'
    )
  ),
  {
    plugins: {
      '@typescript-eslint': fixupPluginRules(typescriptEslint),
      'simple-import-sort': simpleImportSort,
    },
    languageOptions: {
      parser: typescriptParser,
      parserOptions: {
        ecmaVersion: 'latest',
        sourceType: 'module',
        ecmaFeatures: { jsx: true },
      },
    },
    settings: { react: { version: 'detect' } },
    rules: {
      'simple-import-sort/imports': ['error'],
      'simple-import-sort/exports': ['error'],
      'import/first': ['error'],
      'import/newline-after-import': ['error', { count: 1 }],
      '@typescript-eslint/no-explicit-any': 'off',
      '@typescript-eslint/no-unused-vars': ['error', {
        argsIgnorePattern: '^_',
        varsIgnorePattern: '^_',
        caughtErrorsIgnorePattern: '^_',
      }],
      'no-negated-condition': 'error',
    },
  },
];

Lint-Staged Configuration

📄 .lintstagedrc.mjs

import { relative } from 'node:path';
 
function buildEslintCommand(filenames) {
  return `next lint --file ${filenames.map(f => relative(process.cwd(), f)).join(' --file ')}`;
}
 
export default {
  '*.{js,jsx,ts,tsx}': [
    'prettier --write',
    buildEslintCommand,
  ],
  '*.{json,md,yml,yaml,html,css,scss}': ['prettier --write'],
};

Husky Setup

Add to package.json:

"scripts": {
  "prepare": "husky install"
}

Then initialize Husky and add a pre-commit hook:

npx husky install
npx husky add .husky/pre-commit "npx lint-staged"

📄 .husky/pre-commit

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
 
npx lint-staged

📄 .vscode/settings.json

{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.codeActionsOnSave": {
    "source.fixAll": "explicit"
  }
}

Summary

By following this setup, your Next.js + TypeScript project will be clean, consistent, and ready for team collaboration. All code is automatically linted and formatted before every commit — reducing bugs, merge conflicts, and time wasted during reviews.

Related.

Related.

Related.

Related.