Git workflow for TypeScript: how to stop fearing merge conflicts
\n\nIf 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.
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\nWhy does TypeScript require a special approach to Git?
\n\nTypeScript 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.
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
Let's go through each point in order.
\n\n1. Choosing a branching strategy: Git Flow or Trunk-based?
\n\nFor 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\nBasic branch structure
\n\nmain — 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\nExample for a TypeScript project:
\n\ngit 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\nImportant: never rebase onto main if the project has shared types. Use merge — it preserves history and doesn't break dependencies.
\n\n2. Git Hooks: foolproof protection (and protection from yourself)
\n\nThe most common scenario: you forgot to run tsc, committed, pushed, and CI failed. To avoid this, set up pre-commit hooks.
Installing Husky + lint-staged
\n\nHusky is a tool for managing Git hooks. lint-staged runs commands only on changed files.
\n\nnpm install --save-dev husky lint-staged\nnpx husky init\n\n\nCreate the file .husky/pre-commit:
#!/usr/bin/env sh\n. "$(dirname -- "$0")/_/husky.sh"\n\nnpx lint-staged\n\n\nIn package.json add:
{\n "lint-staged": {\n "*.ts": [\n "eslint --fix",\n "prettier --write",\n "tsc --noEmit"\n ]\n }\n}\n\n\nNow before every commit:
\n- \n
- ESLint runs with auto-fix \n
- Code is formatted via Prettier \n
- TypeScript compilation is checked (
tsc --noEmit) \n
If any step fails — the commit won't go through. Harsh, but effective.
\n\n3. Proper commit messages: conventional commits
\n\nTypeScript 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
Examples:
\n\nfeat: add UserRole enum and validation\nfix: c