Apr 19, 2025
In this blog post, I’ll walk you through how to set 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.
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
.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.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"
},
}
];
.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"]
};
Add to package.json
:
"scripts": {
"prepare": "husky install"
}
Add a pre-commit hook:
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"
}
}
ToolPurposeESLintCode quality & error detectionPrettierConsistent code formattingLint-StagedRun Prettier/ESLint only on staged filesHuskyGit hook to run checks before commits
By following this setup, your Next.js + TypeScript project will be clean, consistent, and ready for team collaboration. This configuration ensures all code is linted and formatted before every commit, saving time and reducing code review friction.
Need help extending this setup to include commit linting, CI/CD checks, or GitHub Actions? Drop a message — I'm happy to help!