Git workflow for TypeScript: how to keep your sanity in typed code

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Git workflow for TypeScript: how to stop fearing merge conflicts

\n\n

If you've ever tried to merge two branches where one changed the User interface and the other added a new type to the same module, you know what a developer's existential crisis feels like. TypeScript gives us a powerful type system, but without a proper Git workflow, that power turns into a headache.

\n\n

In this article, we'll break down the optimal Git workflow for TypeScript projects — from branch setup to automatic type checking on every commit. No fluff, only what really works in production.

\n\n

Why does TypeScript require a special approach to Git?

\n\n

TypeScript is not just "JavaScript with types". It's a language where every change can affect dozens of files through interface, type and generics. One wrong edit in a base type — and the entire project stops compiling.

\n\n

Therefore, a standard git workflow for TypeScript should include:

\n\n
    \n
  • Mandatory type checking before commit
  • \n
  • A linter that prevents committing unformatted code
  • \n
  • A clear branching strategy so merge conflicts don't break types
  • \n
  • CI/CD that re-verifies everything on the server
  • \n
\n\n

Let's go through each point in order.

\n\n

1. Choosing a branching strategy: Git Flow or Trunk-based?

\n\n

For TypeScript projects, I recommend Trunk-based development with short-lived branches. Why? Because TypeScript code is, essentially, "raw" code that needs to be quickly checked and deployed. Long branches cause types to "drift apart".

\n\n

Basic branch structure

\n\n
main        — stable version, always ready for deployment\ndevelop     — main development branch\nfeature/*   — new features (live 1-2 days)\nfix/*       — bug fixes\nrelease/*   — release preparation (optional)\n
\n\n

Example for a TypeScript project:

\n\n
git checkout -b feature/add-user-validation\n# work...\ngit commit -m "feat: add email validation for User type"\ngit push origin feature/add-user-validation\n# create PR → merge into develop\n
\n\n

Important: never rebase onto main if the project has shared types. Use merge — it preserves history and doesn't break dependencies.

\n\n

2. Git Hooks: foolproof protection (and protection from yourself)

\n\n

The most common scenario: you forgot to run tsc, committed, pushed, and CI failed. To avoid this, set up pre-commit hooks.

\n\n

Installing Husky + lint-staged

\n\n

Husky is a tool for managing Git hooks. lint-staged runs commands only on changed files.

\n\n
npm install --save-dev husky lint-staged\nnpx husky init\n
\n\n

Create the file .husky/pre-commit:

\n\n
#!/usr/bin/env sh\n. "$(dirname -- "$0")/_/husky.sh"\n\nnpx lint-staged\n
\n\n

In package.json add:

\n\n
{\n  "lint-staged": {\n    "*.ts": [\n      "eslint --fix",\n      "prettier --write",\n      "tsc --noEmit"\n    ]\n  }\n}\n
\n\n

Now before every commit:

\n
    \n
  1. ESLint runs with auto-fix
  2. \n
  3. Code is formatted via Prettier
  4. \n
  5. TypeScript compilation is checked (tsc --noEmit)
  6. \n
\n\n

If any step fails — the commit won't go through. Harsh, but effective.

\n\n

3. Proper commit messages: conventional commits

\n\n

TypeScript projects benefit from conventional commits because:

\n
    \n
  • Changelog is generated automatically
  • \n
  • It's easy to understand which changes break types
  • \n
  • CI can be triggered only on specific commits
  • \n
\n\n

Examples:

\n\n
feat: add UserRole enum and validation\nfix: c                

Blogs

Book Recommendations