This guide explains how to set up Husky and pre-commit protection for your Next.js project to ensure code quality and consistency across your development team.
Husky is a tool that allows you to easily add Git hooks to your project. Git hooks are scripts that run automatically before or after Git commands like commit, push, etc. This setup helps maintain code quality by running checks before code is committed.
lint-staged runs linters on staged files only, which makes the process much faster than running linters on the entire codebase.
- Node.js (v16 or higher)
- Yarn or npm
- Git repository
First, install the required development dependencies:
# Using yarn
yarn add -D husky lint-staged prettier eslint-config-prettier eslint-plugin-prettier
# Using npm
npm install --save-dev husky lint-staged prettier eslint-config-prettier eslint-plugin-prettierCreate or update your .eslintrc.json file:
{
"extends": ["next/core-web-vitals", "prettier"],
"plugins": ["prettier"],
"rules": {
"prettier/prettier": "error",
"no-console": [
"warn",
{
"allow": ["warn", "error"]
}
],
"no-debugger": "error",
"no-alert": "error",
"prefer-const": "error",
"no-var": "error",
"object-shorthand": "error",
"prefer-template": "error"
},
"env": {
"browser": true,
"es2020": true,
"node": true
},
"settings": {
"react": {
"version": "detect"
}
},
"ignorePatterns": [
"node_modules/",
".next/",
"out/",
"build/",
"dist/",
"*.config.js",
"*.config.ts"
]
}Create a .prettierrc file:
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"bracketSpacing": true,
"bracketSameLine": false,
"arrowParens": "avoid",
"endOfLine": "lf",
"quoteProps": "as-needed",
"jsxSingleQuote": true,
"proseWrap": "preserve"
}Create a .prettierignore file to exclude certain files:
# Dependencies
node_modules/
.pnp
.pnp.js
# Production builds
.next/
out/
build/
dist/
# Environment files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Coverage directory used by tools like istanbul
coverage/
# nyc test coverage
.nyc_output
# Dependency directories
jspm_packages/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
public
# Storybook build outputs
.out
.storybook-out
# Temporary folders
tmp/
temp/
# Editor directories and files
.vscode/
.idea/
*.swp
*.swo
*~
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Package files
package-lock.json
yarn.lock
pnpm-lock.yaml
# Config files that should not be formatted
*.config.js
*.config.tsAdd the following scripts to your package.json:
{
"scripts": {
"build": "next build",
"dev": "next dev",
"lint": "next lint",
"lint:fix": "next lint --fix",
"format": "prettier --write .",
"format:check": "prettier --check .",
"type-check": "tsc --noEmit",
"pre-commit": "lint-staged",
"prepare": "husky",
"start": "next start"
}
}Add the lint-staged configuration to your package.json:
{
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write"
],
"*.{json,css,md}": [
"prettier --write"
]
}
}Run the following command to initialize Husky:
yarn prepare
# or
npm run prepareCreate the pre-commit hook file:
npx husky add .husky/pre-commit "yarn pre-commit"Then, replace the content of .husky/pre-commit with:
#!/usr/bin/env sh
[ -n "$CI" ] && exit 0
# Check if lint-staged is installed
if ! command -v lint-staged &> /dev/null; then
echo "lint-staged could not be found, please install it with 'npm install lint-staged' or 'yarn add lint-staged'"
exit 1
fi
echo "π Running pre-commit checks..."
# Run type checking
echo "π Checking TypeScript types..."
yarn type-check || {
echo "β TypeScript errors found. Please fix them before committing."
exit 1
}
# Run linting
echo "π§ Running ESLint..."
yarn lint || {
echo "β ESLint errors found. Please fix them before committing."
exit 1
}
# Run build check
echo "ποΈ Checking build..."
yarn build || {
echo "β Build failed. Please fix build errors before committing."
exit 1
}
# Run lint-staged (formatting and auto-fix)
echo "β¨ Running lint-staged..."
yarn lint-staged || {
echo "β Lint-staged failed. Please fix formatting issues before committing."
exit 1
}
echo "β
All pre-commit checks passed!"Make the hook executable:
chmod +x .husky/pre-commitTest your setup by making a small change and trying to commit:
# Make a change to any file
echo "// test" >> src/test.js
# Try to commit
git add .
git commit -m "test commit"If everything is set up correctly, you should see the pre-commit checks running before the commit is allowed.
- Runs
tsc --noEmitto check for TypeScript errors - Ensures type safety across your codebase
- Checks for code quality issues
- Enforces coding standards
- Catches potential bugs
- Ensures your code can be built successfully
- Catches build-time errors early
- Runs ESLint with auto-fix on staged files
- Formats code with Prettier
- Only processes files that are about to be committed
You can add additional checks to the pre-commit hook:
# Add unit tests
echo "π§ͺ Running tests..."
yarn test || {
echo "β Tests failed. Please fix them before committing."
exit 1
}
# Add security audit
echo "π Running security audit..."
yarn audit || {
echo "β Security vulnerabilities found. Please fix them before committing."
exit 1
}You can customize which files are processed and what actions are taken:
{
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write"
],
"*.{json,css,md,yml,yaml}": [
"prettier --write"
],
"*.{ts,tsx}": [
"eslint --fix",
"prettier --write"
]
}
}In emergency situations, you can skip the pre-commit hooks:
git commit -m "emergency fix" --no-verify-
Hook not running: Make sure the hook file is executable (
chmod +x .husky/pre-commit) -
Permission denied: Run
chmod +x .husky/pre-commit -
lint-staged not found: Install it with
yarn add -D lint-staged -
ESLint errors: Fix the errors manually or run
yarn lint:fix -
Prettier conflicts: Run
yarn formatto format all files
# Skip all hooks for one commit
git commit -m "message" --no-verify
# Skip specific hooks
HUSKY=0 git commit -m "message"- Keep hooks fast: Only run essential checks in pre-commit hooks
- Use lint-staged: Only process staged files for better performance
- Provide clear error messages: Help developers understand what needs to be fixed
- Document your setup: Keep this guide updated for your team
- Regular maintenance: Update dependencies and configurations regularly
For continuous integration, you can run the same checks:
# Example GitHub Actions workflow
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- run: yarn install
- run: yarn type-check
- run: yarn lint
- run: yarn build
- run: yarn testThis setup ensures that your code quality standards are maintained both locally and in your CI/CD pipeline.