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 more comprehensive checks before pushing:
#!/usr/bin/env sh
echo "π Running pre-push checks..."
# Run formatting check on all files
echo "π Checking code formatting..."
npm run format:check || {
echo "β Code formatting check failed!"
echo "π‘ Run 'npm run format' to fix formatting issues"
exit 1
}
# Run linting check
echo "π Running ESLint..."
npm run lint || {
echo "β Linting check failed!"
echo "π‘ Run 'npm run lint:fix' to auto-fix issues"
exit 1
}
# Run type checking
echo "π§ Running TypeScript type check..."
npm run type-check || {
echo "β TypeScript type check failed!"
echo "π‘ Fix type errors before pushing"
exit 1
}
echo "β
All pre-push checks passed! Proceeding with push..."
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:
// package.json scripts
{
"scripts": {
"build": "next build --turbopack",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --write .",
"format:check": "prettier --check .",
"type-check": "tsc --noEmit",
"test": "vitest",
"test:e2e": "playwright test"
}
}
Type Safety with TypeScript
Type checking is crucial for maintaining code quality:
npm run type-check
Benefits:
- 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:
# Unit tests
npm run test
# End-to-end tests
npm run test:e2e
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 recommend setting up branch protection rules for main
and develop
:
# Example GitHub branch protection
Branch Protection Rules:
- Require pull request reviews
- Require status checks to pass
- Require branches to be up to date
- Include administrators
- Restrict pushes to matching branches
Benefits:
- Prevents direct pushes to protected branches
- Requires code review for all changes
- Ensures CI checks pass before merging
- Maintains branch integrity
Environment Management
Development Environment
# Local development
git checkout develop
git pull origin develop
git checkout -b feature/new-feature
# Work on feature
npm run dev
# Before committing
npm run format
npm run lint:fix
npm run type-check
# Commit and push
git add .
git commit -m "feat: add new feature"
git push origin feature/new-feature
Production Deployment
# Merge to main
git checkout main
git merge develop
git tag v1.0.0
git push origin main --tags
# Deploy to production
npm run build
npm run start
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
# Start new feature
git checkout develop
git pull origin develop
git checkout -b feature/user-authentication
# Development cycle
npm run dev
# Make changes
npm run format && npm run lint:fix
git add .
git commit -m "feat: add user login form"
# Push for review
git push origin feature/user-authentication
# Create pull request to develop
Hotfix Process
# Create hotfix from main
git checkout main
git pull origin main
git checkout -b fix/critical-bug
# Fix and test
# Make minimal changes
npm run test
npm run build
# Merge back to both branches
git checkout main
git merge fix/critical-bug
git checkout develop
git merge fix/critical-bug
Release Process
# Prepare release
git checkout develop
git pull origin develop
git checkout -b release/v1.2.0
# Final testing and documentation
npm run test
npm run build
# Merge to main
git checkout main
git merge release/v1.2.0
git tag v1.2.0
git push origin main --tags
# Merge back to develop
git checkout develop
git merge release/v1.2.0
git push origin develop
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.