Building a Professional Git Workflow: From Feature Branches to CI/CD

Featured

How I structured a production-ready Git workflow with proper branching, automated checks, and CI/CD for a Next.js portfolio project.

December 30, 2024
12 min read
git
workflow
ci-cd
branching
husky
linting
typescript

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:

JAVASCRIPT
main     # Production-ready code
develop  # Integration branch for features
feature/ # Individual feature development

Why this structure?

  • main: Always deployable, represents production state
  • develop: Integration branch where features merge before production
  • feature/*: Isolated development environments for each feature

Feature Branch Naming Convention

I use a consistent naming convention for all branches:

JAVASCRIPT
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:

JAVASCRIPT
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:

JAVASCRIPT
// 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
JAVASCRIPT
# .husky/pre-commit
npx lint-staged

Pre-push Hooks: Comprehensive Checks

For additional safety, I run more comprehensive checks before pushing:

JAVASCRIPT
#!/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:

JAVASCRIPT
// 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:

JAVASCRIPT
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:

JAVASCRIPT
# 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:

  1. Code formatting (Prettier)
  2. Linting (ESLint)
  3. Type checking (TypeScript)
  4. Build verification (Next.js build)
  5. Test suite (Vitest + Playwright)

Branch Protection Rules

I recommend setting up branch protection rules for main and develop:

JAVASCRIPT
# 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

JAVASCRIPT
# 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

JAVASCRIPT
# 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

  1. Consistent code quality through automated formatting and linting
  2. Early error detection with pre-commit hooks
  3. Clear feature isolation with branch-based development
  4. Safe experimentation without affecting main codebase

For Teams

  1. Reduced code review time due to automated quality checks
  2. Consistent development practices across all team members
  3. Reduced production bugs through comprehensive testing
  4. Clear deployment process with environment separation

For Projects

  1. Maintainable codebase with consistent formatting and structure
  2. Reliable deployments through automated testing and building
  3. Scalable development process that grows with the team
  4. Professional development practices that impress clients and employers

Common Workflow Patterns

Feature Development

JAVASCRIPT
# 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

JAVASCRIPT
# 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

JAVASCRIPT
# 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

  1. Automated formatting eliminates style debates and ensures consistency
  2. Pre-commit hooks catch issues before they become problems
  3. TypeScript integration provides excellent developer experience
  4. Branch naming conventions make project navigation intuitive

Common Pitfalls to Avoid

  1. Don't skip pre-commit hooks - they save time in the long run
  2. Keep feature branches small - large branches are harder to review
  3. Always test before merging - automated tests should pass locally first
  4. 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.