← back to init_ovi

Commit Like a Pro

Stop writing bad commit messages. A complete conventional commits guide — the standard used by Angular, Vue, Electron, and thousands of open source projects.

A human-readable, machine-parseable standard for writing git commit messages. Based on the Conventional Commits 1.0.0 specification.

Why Conventional Commits?

Most developers write commit messages like this:

fixed stuff
update
wip
changes
ok now it works
final final FINAL

This is useless. Three months later — or to a new contributor — these messages tell you nothing.

Conventional Commits solves this by giving commit messages a consistent, meaningful structure that:

The Full Format

<type>(<scope>): <description>

[optional body]

[optional footer(s)]
PartRequiredDescription
type✅ YesThe category of change
scope❌ NoThe area of the codebase affected
description✅ YesA short summary of the change
body❌ NoDetailed explanation of what and why
footer❌ NoBreaking changes, issue references, co-authors

Rules

Types — Complete Reference

feat — New Feature

Use when you add new functionality visible to the end user or API consumer.

feat: add dark mode toggle
feat(auth): add OAuth2 login support
feat(api): add pagination to /users endpoint

Triggers: MINOR version bump in semantic versioning.

fix — Bug Fix

Use when you fix something that was broken or behaving incorrectly.

fix: resolve crash on empty search input
fix(cart): correct total price calculation
fix(mobile): fix nav menu not closing on tap

Triggers: PATCH version bump in semantic versioning.

chore — Maintenance

Use for tasks that don't modify source code behavior or tests. Common for dependency updates, config tweaks, removing dead files.

chore: update dependencies
chore: remove unused imports
chore: clean up .gitignore
chore(deps): bump lodash from 4.17.20 to 4.17.21

Triggers: No version bump.

docs — Documentation

Use when only documentation files change. This includes README, inline code comments, JSDoc, wikis, changelogs.

docs: add installation guide to README
docs(api): document rate limiting behavior
docs: fix broken links in CONTRIBUTING.md

Triggers: No version bump.

style — Code Style / Formatting

Use for changes that do not affect the meaning of the code. Whitespace, formatting, missing semicolons, import ordering — anything a linter or formatter would touch.

style: format files with prettier
style(button): fix inconsistent spacing
style: remove trailing whitespace
⚠️ Do NOT use this for CSS/UI visual changes. Use feat or fix for those.

Triggers: No version bump.

refactor — Code Restructuring

Use when you rewrite or restructure code without changing its external behavior or fixing a bug.

refactor: extract validation into separate module
refactor(auth): simplify token refresh logic
refactor: replace forEach with map in data transform

Triggers: No version bump.

test — Tests

Use when adding, updating, or fixing tests. No production code changes.

test: add unit tests for useAnimeList hook
test(api): add integration tests for /search endpoint
test: fix flaky timeout in auth tests

Triggers: No version bump.

perf — Performance

Use when you make a change that improves performance without changing behavior.

perf: debounce search input to reduce API calls
perf(images): add lazy loading to anime cards
perf: memoize expensive genre filter computation

Triggers: PATCH version bump in some configurations.

ci — Continuous Integration

Use for changes to CI/CD configuration files and scripts.

ci: add automated deployment to Vercel
ci: fix failing lint step in GitHub Actions
ci(docker): update Node.js base image to 20-alpine

build — Build System / Dependencies

Use for changes that affect the build system, build tools, or external dependencies.

build: migrate from CRA to Vite
build: add path aliases to tsconfig
build(deps): add recharts as dependency

revert — Reverting a Commit

Use when reverting a previous commit. The body should include the hash of the reverted commit.

revert: revert "feat: add experimental offline mode"

Reverts commit a1b2c3d.
Caused a regression in the login flow on Safari.

Extended / Less Common Types

TypeWhen to use
securityPatching a security vulnerability
wipWork in progress — should never stay on main
hotfixUrgent production fix
i18nInternationalization and localization changes
a11yAccessibility improvements
infraInfrastructure changes (Terraform, Docker, K8s)

Scopes

A scope is an optional piece in parentheses that narrows what part of the codebase the commit touches.

feat(auth): add JWT refresh logic
fix(navbar): resolve dropdown z-index issue
chore(deps): update react to 18.3.0

Rules for Scopes

Common Scope Patterns

# By feature
feat(auth): ...
feat(chat): ...
feat(search): ...

# By layer
fix(api): ...
fix(ui): ...
fix(db): ...

# By platform
fix(mobile): ...
fix(web): ...
fix(desktop): ...

Description Rules

Commit Body

Use the body to explain what changed in more detail, why the change was made, what problem it solves, and any trade-offs or side effects.

feat(chat): wire recommendations through AniList API

Previously AniBuddy pulled recommendations from a static local array
of 20 hardcoded titles. This meant every user got the same suggestions
regardless of what genres they asked for.

Now recommendations query the AniList GraphQL API directly, filtering
by genre and sorting by popularity. Results are dynamic and always
current.

Note: adds a 300-500ms delay on recommendation requests due to the
API call. Debounced to avoid hammering the endpoint.

Footers

Footers go after the body (separated by a blank line) and follow a Token: value format.

# Referencing Issues
Closes #42
Fixes #17
Resolves #88
Related to #55

# Co-authors
Co-authored-by: Name <email@example.com>

# Breaking Change in Footer
BREAKING CHANGE: `exportList` now returns a Promise instead of void.

Full Example with Footers

feat(api): replace REST endpoints with GraphQL

The previous REST implementation required multiple round trips to fetch
anime details, related series, and character data separately.

GraphQL allows all of this in a single query, reducing load time on
the details sheet from ~1200ms to ~280ms on average.

Closes #34
Co-authored-by: Tahsin Zidane <tahsin@example.com>

Breaking Changes

A breaking change is any change that requires users or consumers of your code to update their own code. There are two ways to signal it:

Method 1 — Exclamation Mark

feat!: change exportList to return Promise
feat(api)!: remove deprecated v1 endpoints

Method 2 — Footer

feat(api): update authentication flow

BREAKING CHANGE: API tokens are no longer accepted as query params.
All requests must include the Authorization header instead.

Both Together (recommended)

feat(api)!: remove query param authentication

BREAKING CHANGE: API tokens are no longer accepted as query params.
All requests must include the Authorization header instead.
Migration guide: https://docs.example.com/migration/v2

Triggers: MAJOR version bump in semantic versioning.

What NOT to Do

Vague descriptions

# Bad
fix: fix
feat: update
chore: stuff
fix: oops

# Good
fix: prevent crash when anime list is empty
feat: add episode progress tracking to anime cards
chore: remove unused StatusBadge variant

Wrong type

# Bad — this is a fix, not a style change
style: fix broken layout on mobile

# Bad — this is a feat, not a refactor
refactor: add search history dropdown

# Good
fix: fix broken layout on mobile
feat: add search history dropdown

Too many changes in one commit

# Bad — this should be 3 commits
feat: add dark mode, fix mobile nav, update README

# Good — three separate commits
feat: add dark mode toggle
fix(nav): fix mobile menu not closing on route change
docs: update README with dark mode usage

Versioning & Semantic Release

Conventional Commits maps directly to Semantic Versioning (MAJOR.MINOR.PATCH):

Commit TypeVersion BumpExample
featMINOR — 1.0.0 → 1.1.0New feature added
fixPATCH — 1.0.0 → 1.0.1Bug fixed
perfPATCH — 1.0.0 → 1.0.1Performance improved
BREAKING CHANGE / !MAJOR — 1.0.0 → 2.0.0API changed
Everything elseNo bumpDocs, chores, style, etc.

Tools & Integrations

Changelog Generation

ToolDescription
conventional-changelogGenerate changelogs from commit history
git-cliffHighly configurable changelog generator
release-pleaseGoogle's automated release PR tool

Commit Message Linting

ToolDescription
commitlintLint commit messages against a ruleset
commitizenInteractive CLI for writing commit messages

Commit Message Linting Setup

Use commitlint to enforce the convention automatically on every commit.

Install

npm install --save-dev @commitlint/cli @commitlint/config-conventional

Configure — commitlint.config.js

export default {
  extends: ['@commitlint/config-conventional'],
};

Test it manually

echo "feat: add login page" | npx commitlint
# passes

echo "added login page" | npx commitlint
# fails with error

Git Hooks Setup with Husky

npm install --save-dev husky
npx husky init
echo "npx --no -- commitlint --edit \$1" > .husky/commit-msg

Now every commit will be linted before it's accepted. If the message doesn't follow the convention, the commit is rejected with a clear error.

Optional: Commitizen for interactive commits

npm install --save-dev commitizen cz-conventional-changelog

Add to package.json:

{
  "config": {
    "commitizen": {
      "path": "cz-conventional-changelog"
    }
  }
}

Run npx cz instead of git commit to get a guided prompt.

Team Workflow Tips

  1. Agree on scopes upfront — Define a list of allowed scopes and document them. Consistency matters more than the specific names.
  2. One logical change per commit — If your description needs the word "and", consider splitting it into two commits.
  3. Commit often, squash before merging — Work in small WIP commits locally, then squash into clean conventional commits before opening a PR.
  4. Use the body for context, not summary — The description is what. The body is why. Future maintainers care more about why.
  5. Reference issues whenever possibleCloses #42 in the footer creates a paper trail between your code changes and the original task.
  6. Enforce with tooling, not discipline — Human discipline fails. Set up commitlint + Husky so the rules are enforced automatically.

Quick Cheatsheet

FORMAT
──────────────────────────────────────────────────────
<type>(<scope>): <description>

[optional body]

[optional footer(s)]


TYPES
──────────────────────────────────────────────────────
feat        New feature              → MINOR version bump
fix         Bug fix                  → PATCH version bump
chore       Maintenance, no change   → no bump
docs        Documentation only       → no bump
style       Formatting, no logic     → no bump
refactor    Restructure, no behavior → no bump
test        Tests only               → no bump
perf        Performance improvement  → PATCH version bump
ci          CI/CD config             → no bump
build       Build system / deps      → no bump
revert      Revert a commit          → depends
feat! / BREAKING CHANGE              → MAJOR version bump


DESCRIPTION RULES
──────────────────────────────────────────────────────
✅ Imperative mood   → "add feature" not "added feature"
✅ Lowercase start   → "fix bug" not "Fix bug"
✅ No period at end  → "update config" not "update config."
✅ Under 72 chars    → keep it short and specific


FOOTER TOKENS
──────────────────────────────────────────────────────
Closes #42
Fixes #17
BREAKING CHANGE: description of what broke
Co-authored-by: Name <email>


EXAMPLES
──────────────────────────────────────────────────────
feat: add anime search by genre
feat(chat): wire recommendations through AniList API
fix: resolve crash on empty input
fix(anilist): handle null description gracefully
chore: remove lovable-tagger boilerplate
chore(deps): bump vite from 5.4.0 to 5.4.19
docs: add setup instructions to README
style: format files with prettier
refactor: extract query strings into constants
test: add unit tests for useAnimeList hook
perf: debounce search input to reduce API calls
ci: add GitHub Actions deploy workflow
build: migrate bundler from webpack to vite
revert: revert "feat: add experimental offline mode"
feat!: rename AnimeStatus values to uppercase

Further Reading

Made by Ovi ren view on GitHub →