Lefthook vs Husky: Why I Switched and Never Looked Back
Lefthook replaces Husky and lint-staged with a single Go binary, parallel hook execution, and built-in file filtering. Here is why I switched and kept going.
10 May 2026

For years I set up Husky in every JavaScript project without thinking twice. It was the obvious choice, the one every tutorial reached for, the one already in every package.json template. Then I tried Lefthook on a whim and ended up ripping Husky out of several repos in the same week.
This is not a "both tools are good in different ways" article. Lefthook is better. Here is why.
What we are actually solving
Git hooks are shell scripts that fire at specific points in the git workflow: before a commit, before a push, after a merge, and so on. They live in .git/hooks/ and are not tracked by git, which means every developer who clones your repo needs a way to install them.
Both Husky and Lefthook solve that problem. But they go about it differently, and that difference shows up in ways that matter in real teams.
How Husky works
Husky became popular because it solved the hooks-not-tracked problem elegantly for the npm ecosystem. You add a prepare script to package.json, and npm install sets everything up automatically.
{
"scripts": {
"prepare": "husky"
}
}
Then you create shell files in a .husky/ directory:
# .husky/pre-commit
npx lint-staged
It works. But there are cracks.
The first thing that bothered me was that Husky is a Node.js tool managing shell scripts. Every hook is a separate file. Adding a new hook means creating a new file, remembering the right filename, making it executable, and keeping it in sync across the team. When you have five hooks, that is five files, each doing its own thing, with no shared configuration.
Then there is the sequential execution problem. By default, Husky runs everything in sequence. If your pre-commit hook runs lint, type-check, and tests, they run one after another. On a large project this adds up fast. I have worked on repos where the pre-commit hook took over 90 seconds because nobody ever questioned the default.
Husky also has a rough relationship with monorepos. If you have multiple packages and want to run the correct linter for each one, you end up writing shell logic inside the hook files. It gets messy.
And then there is the subtle annoyance: Husky requires Node.js to be installed. That is fine for pure JS projects, but the moment you have a backend service written in Go or Python sitting in the same repo, your Go engineers now need Node.js on their machine just to commit code.
How Lefthook solves Git hooks differently
Lefthook is a single binary written in Go. There is no runtime dependency, no node_modules, no prepare script. You install it once (via Homebrew, asdf, or your package manager of choice), run lefthook install, and you are done.
brew install lefthook
lefthook install
Configuration lives in a single lefthook.yml file at the repo root:
pre-commit:
parallel: true
commands:
lint:
run: pnpm lint
typecheck:
run: pnpm typecheck
pre-push:
commands:
tests:
run: pnpm test
That is the whole thing. One file, readable at a glance, checked into version control.
The parallel: true line is where things get interesting. Lefthook can run all commands in a hook stage concurrently. Your lint and typecheck run at the same time instead of waiting for each other. On a typical frontend project I have seen pre-commit times drop from 45 seconds to around 15 seconds just from enabling parallelism.
Filtering by changed files
One of the most useful features is file filtering. You can tell a command to only run against files that changed in the current commit:
pre-commit:
commands:
lint:
glob: '*.{ts,tsx}'
run: pnpm eslint {staged_files}
{staged_files} is a template variable Lefthook replaces with the actual staged files matching your glob. This is what lint-staged does, but baked directly into the hook runner without needing another dependency.
With Husky you need lint-staged as a separate tool, configured separately, invoked separately. With Lefthook it is just part of the config.
Monorepo support
Lefthook handles monorepos properly. You can scope commands to specific directories. If you are still deciding on your monorepo tooling overall, the comparison of NX, Turborepo, Bazel, and Rush covers the broader picture of what sits above the hook layer.
pre-commit:
commands:
lint-frontend:
root: 'packages/frontend/'
run: pnpm lint
lint-backend:
root: 'packages/backend/'
run: go vet ./...
Each command runs from its own root. No shell gymnastics, no cd chains inside hook files.
Migration from Husky
If you are on Husky today, switching is straightforward. Remove Husky and lint-staged from your dependencies, delete the .husky/ directory, remove the prepare script from package.json, and create a lefthook.yml with your hooks translated to YAML. Then lefthook install.
The translation from shell scripts to YAML commands is mostly mechanical. The main adjustment is thinking in commands rather than scripts, which is actually simpler. Getting hooks right also feeds directly into cleaner code review: when lint and type errors are caught at commit time, reviewers spend their attention on design rather than formatting.
The one place Husky still makes sense
If your team is entirely JavaScript-focused and everyone is comfortable with the prepare-script setup, Husky works fine for simple cases. The ecosystem knows it. Onboarding docs everywhere assume it. That familiarity has real value.
But if you are in a polyglot repo, a monorepo, or you care about hook execution time, Lefthook is the better tool. It is faster to configure, faster to run, and does not require Node.js on machines that have no business running Node.js.
I replaced Husky with Lefthook across my main projects last quarter and have not had a reason to look back. The fact that this site's own repo now uses Lefthook for pre-commit checks is probably the most honest endorsement I can give.
If you are thinking through tooling decisions like this and want to talk through the trade-offs for your specific setup, my mentorship calendar is open. Or if you want more of this kind of opinionated take on engineering tools, the Monday by Gazar newsletter is where I publish it first.