Skip to content

Biome and Lefthook: two tools to replace your whole linting setup

If you have ever bootstrapped a JavaScript or TypeScript project and ended up with Prettier, ESLint, @typescript-eslint/parser, @typescript-eslint/eslint-plugin, eslint-config-prettier, Husky, lint-staged, and Commitlint all sitting in your devDependencies: this could be of interest to you. Biome and Lefthook replace all of that with two packages, almost no config, and faster runs.

Biome

Biome is a formatter and linter in one, written in Rust. It replaces Prettier for formatting and ESLint (plus the TypeScript plugins) for linting. One package, one config file.

pnpm add -D -E @biomejs/biome
pnpm biome init

biome init drops a biome.json with sensible defaults. A minimal setup looks like this:

{
  "$schema": "https://biomejs.dev/schemas/2.4.16/schema.json",
  "formatter": {
    "indentStyle": "space",
    "indentWidth": 2
  },
  "javascript": {
    "formatter": {
      "quoteStyle": "single",
      "semicolons": "asNeeded"
    }
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true
    }
  }
}

To check your files and fix what can be fixed automatically:

pnpm biome check .           # report issues
pnpm biome check --write .   # report and fix

One caveat worth knowing: Biome does not have every ESLint rule. The recommended ruleset covers the important ones and the list keeps growing, but if you rely on a specific ESLint plugin, check the Biome docs before switching.

Lefthook

Lefthook is a git hooks manager written in Go. It replaces Husky for running hooks and Commitlint for validating commit messages. Because it’s a compiled binary rather than a Node.js script, it starts up fast and you stop noticing it’s there.

pnpm add -D lefthook
pnpm lefthook install

lefthook install wires things up inside your .git/hooks folder. The configuration lives in a lefthook.yml at the root of your project:

pre-commit:
  parallel: true
  jobs:
    - name: biome
      glob: '*.{js,ts,jsx,tsx,json,css}'
      run: pnpm biome check --no-errors-on-unmatched --files-ignore-unknown=true {staged_files}

commit-msg:
  jobs:
    - name: message-format
      run: >
        head -1 {1} | grep -qE '^(feat|fix|hotfix|chore)(\(.+\))?: .+'
        || (echo 'Invalid commit message. Expected format: feat|fix|hotfix|chore: short comment' && exit 1)

A few things to notice here. The pre-commit hook runs Biome only on staged files (Lefthook injects them as {staged_files}), so even large projects stay fast. The commit-msg hook checks that your message matches a format before the commit goes through. In this example that format is feat|fix|hotfix|chore: short description, but you can change the regex to whatever convention your team uses.

Putting it together

Once both are installed, a commit looks like this:

git add src/components/header.tsx
git commit -m 'feat: add sticky header'

Lefthook runs Biome on your staged files first. If anything fails the check, the commit stops. If the files are clean, it validates the commit message. Two guardrails, no extra Node.js overhead.

Here is the full list of packages this replaces:

  • prettier
  • eslint
  • @typescript-eslint/parser
  • @typescript-eslint/eslint-plugin
  • eslint-config-prettier
  • husky
  • lint-staged
  • commitlint + a config package

That is eight or more packages down to two, with faster cold starts and a single config file for each tool.

Resources