Building a Professional Git Workflow: From Feature Branches to CI/CD
After working on multiple projects, I've learned that a solid Git workflow isn't just about version control—it's about maintaining code quality, enabling safe deployments, and scaling development processes. In this post, I'll walk through the Git workflow and CI/CD setup I implemented for my portfolio project, explaining the decisions behind each choice and how they benefit both individual developers and teams.
The Challenge: Scaling Development Practices
When I started this portfolio project, I needed a workflow that would:
- Maintain code quality across all commits
- Enable safe feature development without breaking production
- Support multiple developers working on different features
- Automate repetitive tasks like formatting and testing
- Provide clear deployment paths for different environments
The solution was a structured branching strategy combined with automated CI/CD pipelines.
Branching Strategy: Git Flow for Modern Development
Core Branches
I implemented a Git Flow-inspired branching strategy with three main branches:
main # Production-ready code
develop # Integration branch for features
feature/ # Individual feature development
Why this structure?
main: Always deployable, represents production statedevelop: Integration branch where features merge before productionfeature/*: Isolated development environments for each feature
Feature Branch Naming Convention
I use a consistent naming convention for all branches:
feature/about # New features
fix/build-configuration # Bug fixes
chore/add-animations # Maintenance tasks
ci/improve-pipeline # CI/CD improvements
Benefits:
- Clear categorization of changes at a glance
- Easy filtering in pull request lists
- Automated tooling can parse branch types for different workflows
Branch Lifecycle
Here's how a typical feature flows through the system:
graph LR
A[develop] --> B[feature/new-feature]
B --> C[Pull Request]
C --> D[Code Review]
D --> E[develop]
E --> F[Release]
F --> G[main]
Automated Quality Gates: Pre-commit and Pre-push Hooks
Pre-commit Hooks with Husky
I use Husky to run automated checks before commits are created:
// package.json
{
"lint-staged": {
"*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{json,md,mdx,css,yml,yaml}": ["prettier --write"]
}
}
What this does:
- Lints staged files with ESLint and auto-fixes issues
- Formats code with Prettier for consistency
- Only processes changed files for efficiency
- Prevents commits if fixes fail
# .husky/pre-commit
npx lint-staged
Pre-push Hooks: Comprehensive Checks
For additional safety, I run comprehensive checks before pushing: formatting validation, ESLint, and TypeScript type checking. These hooks catch issues locally before they reach the remote repository, saving CI/CD resources and providing immediate feedback.
Why pre-push hooks?
- Catches issues early before they reach the remote repository
- Prevents broken code from being shared with the team
- Saves CI/CD resources by catching problems locally
- Provides immediate feedback to developers
CI/CD Pipeline: Automated Quality Assurance
Build Process
The CI/CD pipeline runs several automated checks: code formatting (Prettier), linting (ESLint), TypeScript type checking, build verification, and test suites (unit tests with Vitest and E2E tests with Playwright). Each check must pass before code can be merged.
Type Safety with TypeScript
Type checking catches errors before runtime and provides better IDE support with autocomplete and error detection. It makes refactoring safer by catching breaking changes and improves code documentation through type definitions.
- Catches type errors before runtime
- Provides better IDE support with autocomplete and error detection
- Makes refactoring safer by catching breaking changes
- Improves code documentation through type definitions
Testing Strategy
I implement multiple levels of testing following the testing pyramid:
- Unit tests: Individual functions and components
- Integration tests: Component interactions
- E2E tests: Full user workflows
Pull Request Workflow: Quality Gates
Required Checks
All pull requests must pass:
- Code formatting (Prettier)
- Linting (ESLint)
- Type checking (TypeScript)
- Build verification (Next.js build)
- Test suite (Vitest + Playwright)
Branch Protection Rules
I set up branch protection rules for main and develop that require pull request reviews, ensure all status checks pass, require branches to be up to date, and prevent direct pushes. This maintains branch integrity and ensures code quality.
- Prevents direct pushes to protected branches
- Requires code review for all changes
- Ensures CI checks pass before merging
- Maintains branch integrity
Environment Management
Development Workflow
My typical development workflow starts by checking out develop, pulling the latest changes, and creating a new feature branch. I work on the feature locally, run formatting and linting before committing, then push the branch and create a pull request for review.
Production Deployment
When ready for production, I merge the feature branch to develop, then create a release branch for final testing. After validation, I merge to main, tag the release with a version number, and deploy. This process ensures only tested, reviewed code reaches production.
Benefits of This Workflow
For Individual Developers
- Consistent code quality through automated formatting and linting
- Early error detection with pre-commit hooks
- Clear feature isolation with branch-based development
- Safe experimentation without affecting main codebase
For Teams
- Reduced code review time due to automated quality checks
- Consistent development practices across all team members
- Reduced production bugs through comprehensive testing
- Clear deployment process with environment separation
For Projects
- Maintainable codebase with consistent formatting and structure
- Reliable deployments through automated testing and building
- Scalable development process that grows with the team
- Professional development practices that impress clients and employers
Common Workflow Patterns
Feature Development
When starting a new feature, I create a branch from develop, work on the feature locally, run quality checks before committing, then push and create a pull request. This keeps features isolated and makes code review straightforward.
Hotfix Process
For critical bugs in production, I create a hotfix branch directly from main, make minimal changes to fix the issue, test thoroughly, then merge back to both main and develop to keep branches in sync.
Release Process
When preparing a release, I create a release branch from develop for final testing and documentation. Once validated, I merge to main, tag the version, and merge back to develop to include the release changes in ongoing development.
Lessons Learned
What Works Well
- Automated formatting eliminates style debates and ensures consistency
- Pre-commit hooks catch issues before they become problems
- TypeScript integration provides excellent developer experience
- Branch naming conventions make project navigation intuitive
Common Pitfalls to Avoid
- Don't skip pre-commit hooks - they save time in the long run
- Keep feature branches small - large branches are harder to review
- Always test before merging - automated tests should pass locally first
- Document the workflow - new team members need clear guidelines
Tools and Technologies
Core Tools
- Git: Version control and branching
- Husky: Git hooks management
- lint-staged: Efficient pre-commit processing
- ESLint: Code linting and quality
- Prettier: Code formatting
- TypeScript: Type safety and developer experience
CI/CD Tools
- GitHub Actions: Automated workflows
- Next.js: Build and deployment
- Vitest: Unit testing
- Playwright: End-to-end testing
Conclusion
A well-structured Git workflow isn't just about managing code—it's about building a foundation for professional development practices. The workflow I've implemented provides:
- Quality assurance through automated checks
- Team collaboration through clear branching strategies
- Safe deployments through environment separation
- Scalable processes that grow with your project
The key is starting simple and gradually adding complexity as your project grows. Begin with basic branching, add automated formatting, then introduce more sophisticated CI/CD as needed.
Remember: the best workflow is one that your team actually uses consistently. Start with the essentials and build from there.
This workflow has been battle-tested on multiple projects and continues to evolve as I learn new best practices. Feel free to adapt these patterns to fit your specific project needs.