Part 02: Streamline Your Dev Workflow: Husky, Lint-Staged, & Commitlint for Next.js
This is the second article of “Recommended Next.js Libraries & Packages” article series. Here I explain how to streamline your developer workflow using husky, lint-staged, & commitlint. I have provided the relevant codes for each step to get a better understanding.
Check out the first article on Configure ESLint and Prettier into Next.js projects in this article series to start from the beginning.
Let’s get started…!
Concepts
1. Git Hooks
Git hooks are a powerful toolset we can use to automate tasks and enforce policies. GIT provides several git hooks such as pre-commit, commit-msg, post-commit, pre-push, etc. You can get an idea of the execution point of each hook from the above image. As I already mentioned, we can enforce policies with Git hooks. So, we can prevent the execution of operations if it violates a policy. Here is a brief overview of some common git hooks
- pre-commit: This hook is useful to validate linting rules (ESLint, Prettier), and run unit tests kind of scenarios.
- commit-msg: Here we validate the commit message according to conventions like conventional commits.
- pre-push: Useful to check whether we will push sensitive information to the remote repository.
We can use a tool like Husky to manage git-hooks easily. You can view an example use case of Husky in the implementation section of this article.
2. Conventional Commits
Conventional-commits is a popular convention used to write better commit messages. It consists of a well-defined rule set. By following them, we can maintain a better commit log. It also generates Changelogs automatically.
Here below is the structure of a commit message.
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
Conventional-commits have specifications for each property in a commit message (type, scope, description, body, footer). You can get a better understanding of those conventions by this link.
We can make a rigid workflow with conventional-commits using automation tools like commitlint. We further discuss that in the implementation section.
Packages (With Implementation)
- Husky
Husky is a tool we use to manage Git Hooks easily. With Husky, we can define the hooks (as scripts) we need. After that, it takes the responsibility to trigger the hooks at the appropriate execution point.
1.1. Install husky
npm install --save-dev husky
1.2. Create a “pre-commit” file
This command creates a “pre-commit” script in .husky/ and updates the “prepare” script in package.json.
npx husky init
Created “pre-commit” file content
npm run format:fix
npm run lint
2. Lint-Staged
Lint-Staged is a useful tool to run linters in staged files only. Without this package, linters run though the whole project and consume unnecessary time to check already linted codes.
2.1. Install lint-staged
npm install --save-dev lint-staged
2.2. Update scripts object inside “package.json”
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"lint-staged": "lint-staged", // Added script
"format": "prettier --check .",
"format:fix": "prettier --write --list-different .",
"prepare": "husky"
}
2.3. Update “pre-commit” file content
npm run lint-staged --concurrent false
2.4. Create “.lintstagedrc.json” file in the root level
This is the configuration file of lint-staged. It defines the linters for different file patterns.
According to these configurations, prettier and eslint automatically fix linting issues. It helps to save time spent on manual fixes.
{
"*.{js,jsx,ts,tsx}": ["prettier --write", "eslint --fix", "eslint"],
"*.{json,md,yml}": ["prettier --write"]
}
3. Commitlint
3.1. Install dependencies
npm install --save-dev @commitlint/{cli,config-conventional} @commitlint/types conventional-changelog-atom
3.2. Create “commitlint.config.ts” file in the root level
This is the configuration file for conventional-commits. You have to follow these rules when you are adding a commit.
You can change the rules as you need. Here are the available rules
import type { UserConfig } from '@commitlint/types';
const Configuration: UserConfig = {
extends: ['@commitlint/config-conventional'],
parserPreset: 'conventional-changelog-atom',
formatter: '@commitlint/format',
rules: {
'type-enum': [
2,
'always',
[
'feat', // New feature
'fix', // Bug fix
'docs', // Documentation changes
'style', // Changes that do not affect the meaning of the code (white-space, formatting, etc.)
'refactor', // Code changes that neither fix a bug nor add a feature
'perf', // Performance improvement
'test', // Adding missing tests or correcting existing tests
'build', // Changes that affect the build system or external dependencies (example scopes: npm)
'ci', // Changes to CI configuration files and scripts
'chore', // Other changes that don't modify src or test files
'revert', // Reverts a previous commit
],
],
'scope-enum': [
2,
'always',
[
'setup', // Project setup
'config', // Configuration files
'deps', // Dependency updates
'feature', // Feature-specific changes
'bug', // Bug fixes
'docs', // Documentation
'style', // Code style/formatting
'refactor', // Code refactoring
'test', // Tests
'build', // Build scripts or configuration
'ci', // Continuous integration
'release', // Release related changes
'other', // Other changes
],
],
},
};
export default Configuration;
3.3. Update “tsconfig.json” file content
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
"commitlint.config.ts" // Added line
],
"exclude": ["node_modules"]
}
3.4. Create “commit-msg” file inside the .husky folder
npx --no -- commitlint --edit $1
Congratulations! You have successfully streamlined your developer workflow using task automation.
Now try to add a commit message
- without staging
- without following commitlint rules
If you have properly configured your setup, you can’t add that commit message. You need to stage the desired files and write the commit message by following commitlint rules to make a successful commit. This prevents meaningless commit messages.
// Example for a valid commit message
feat(setup): add commitlint for commit message validation
Useful Links:
- Git Hooks: https://githooks.com/
- Conventional Commits: https://www.conventionalcommits.org/en/v1.0.0/
- husky: https://typicode.github.io/husky/get-started.html
- lint-staged: https://www.npmjs.com/package/lint-staged
- commitlint: https://commitlint.js.org/guides/getting-started.html