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-prettier
Create 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.ts
Add 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 prepare
Create 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-commit
Test 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 --noEmit
to 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 format
to 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 test
This setup ensures that your code quality standards are maintained both locally and in your CI/CD pipeline.