What is husky?
Husky is a JavaScript package that allows you to run some code during various parts of your git workflow. Husky leverages git hooks to allow you to hook into various git events such as pre-commit and pre-push.
What is lint-staged?
Lint-staged allows you to run code against your staged git files to automate the tedious part of your workflows, such as formatting with Prettier and/or linting with ESLint.
Using husky with lint-staged
You can use husky to trigger lint-staged during the pre-commit hook, so that your coding standards are enforced right as you commit. The major benefit of this is it enforces your coding standards without someone needing to configure and install certain extensions in their IDE to enforce them on save or remembering to do anything. Your code gets fixed before it ever leaves your machine, so you don’t have to wait for your CI to inform you that you forgot to run the formatter or linter.
The Problem: husky expects your package.json at the root
The problem is, husky expects your package.json
to be at the root of your project. That’s a fine assumption to make a lot of times, but sometimes we might be in more of a monorepo situation, or where a single repo contains both the Server and Client in separate folders.
The Solution
I’m going to use the example from the default React and ASP.NET Core template when you run dotnet new react
. The default folder structure has the React code nested under a /ClientApp folder with the corresponding package.json
. The folder structure looks like this:

Let’s dive into the steps. You can view the completed repo here.
cd
into the directory with yourpackage.json
- In my case:
./ClientApp
- In my case:
- Install husky and lint-staged:
npm i husky lint-staged -D
- Add a “prepare” npm script to your
package.json
with the following contents (note: the initialcd
goes to the repo root and thehusky install
goes from the repo root down to your directory with thepackage.json
):
{
"scripts": {
// other scripts omitted
"prepare": "cd ../ && husky install ./ClientApp/.husky"
}
}
- Run
npm install
- FYI – a
.husky
folder will appear in the same path as yourpackage.json
, in my case under./ClientApp
- Create a
pre-commit
file with no file extension under the.husky
folder with the following contents (note: the./ClientApp
is the path to yourpackage.json
relative to the root of your repository):
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
cd ./ClientApp && npx lint-staged
- Make sure the
pre-commit
file is executable viachmod
:
chmod +x ./.husky/pre-commit
- Add a
.lintstagedrc
file under your ./ClientApp folder (or wherever yourpackage.json
lives) with the following contents:
{
"*.{js,ts,tsx,scss,css,md}": ["eslint", "prettier --write"]
}
As you can probably guess, the above says look for any changes to any of the file extensions defined on the left, and run the commands in the array on the right if you find any changes to those files.
And that’s it! Now if you try to make a commit, you will see that eslint and prettier will run and fix themselves as you would expect. See an example of it in action below:

[…] Using Husky Git Hooks and Lint-Staged With Nested Folders (Scott Sauber) […]
[…] like to learn more about how to enforce Prettier via git commit hooks, checkout my blog post on Husky and Lint Staged with Prettier that even works with nested […]
This was very helpful. Thanks for sharing!
Thanks for letting me know it was useful!
Thanks for the great article. If anyone has issue where eslint fails/errors when trying to commit. Try adding
--fix
flag to the lintstaged eslint command. So:{
“*.{js,ts,tsx,scss,css,md}”: [“eslint –fix”, “prettier –write”]
}
Thank you for the article. This was enlightening.
Thanks Andrei. Glad it was useful.
Thanks for the information, it was very helpful 🙂
Thanks Eran! Glad to hear