May 17, 2025
Clean & Safe Prisma Migrations GuideIn 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 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
📄 .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"
}
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"
}
}
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.