Your monorepo contains 50 services, 200 packages, and contributions from 15 teams across three time zones. A feature branch that touches the shared authentication library potentially affects every service. Another branch modifies just one microservice but needs to coordinate with infrastructure changes happening in parallel. Someone else's branch has been open for two weeks, blocking three other features that depend on it.
This is feature branch management at scale. The patterns that work beautifully for a five-person team working on a single application break down when you're coordinating across teams, services, and deployment boundaries in a single repository.
Large repositories and monorepos introduce challenges that smaller codebases never encounter: coordinating changes across teams, managing dependencies between services, handling deployment complexity, and keeping CI/CD pipelines fast when the codebase is massive. The solution isn't abandoning feature branches—it's adapting the workflow to handle scale.
The Unique Challenges of Monorepo Feature Branches
Feature branches in monorepos face problems that don't exist in smaller repositories.
Dependency Coordination
In a monorepo, packages depend on each other. A feature branch that updates a shared library's interface breaks every service that uses that library. You can't merge until you've updated all consumers—but those consumers might be owned by different teams who don't know your changes are coming.
Consider this scenario: Team A wants to add authentication tokens to the API client library. This change requires updates in 15 services that use the library. Some of those services are maintained by Team B, who's focused on their own deadlines. Others are legacy services with unclear ownership.
Without coordination, Team A's feature branch grows enormous—thousands of lines changing dozens of files across multiple services. The PR becomes difficult to review, conflicts accumulate daily, and merging becomes risky. The longer it takes, the more conflicts pile up, creating a vicious cycle.
CI/CD Performance at Scale
When every feature branch runs the full test suite for all 50 services, pipelines take hours. Developers push changes in the morning and don't get feedback until afternoon. This destroys the tight feedback loop that makes feature branches effective.
Running only affected tests helps, but computing affected packages has its own complexity. Change a shared utility function and suddenly 40 packages are affected. The CI system needs to build dependency graphs, determine what tests to run, and execute them efficiently—all while multiple feature branches compete for resources.
Merge Conflicts and Churn
In active monorepos, main changes constantly. During the two weeks your feature branch lives, main might receive 200 commits from 30 different developers. Keeping your branch current requires frequent merges or rebases, each potentially introducing conflicts.
The conflict rate increases with repository size and team size. More developers making more changes means higher probability that someone touched the same code you're modifying. In a five-person team, you might encounter conflicts weekly. In a 50-person team, you might hit them daily.
Deployment Coordination
Many monorepos contain multiple deployable services. Your feature branch might change three services that deploy independently. Do you deploy them together? Separately? What if Service A's changes depend on Service B's changes landing first?
Feature branches that span multiple services need deployment orchestration. You can't simply merge and deploy—you need to ensure services deploy in the right order, feature flags coordinate behavior across services, and rollback procedures work when services are interdependent.
Scaling Feature Branch Workflows
Effective monorepo workflows adapt traditional feature branch patterns to handle these challenges.
Scope-Limited Branches
Instead of massive branches that touch everything, create focused branches that change one service or package:
# Bad: Enormous branch touching 15 services feature/add-auth-tokens # Changes 15 services, 120 files # Good: Separate branches for each piece feature/api-client-auth-tokens # Changes shared library feature/user-service-auth-tokens # Updates one service feature/payment-service-auth-tokens # Updates another service
This breaks large changes into manageable pieces. Each branch can merge independently once its piece is complete. Services adopt the new library interface gradually rather than all at once.
Yes, this means more coordination and more PRs, but each PR is reviewable, testable, and mergeable without coordinating 15 teams.
CODEOWNERS for Routing Reviews
Monorepos with many teams need automatic review routing. CODEOWNERS files specify which teams own which parts of the codebase:
# .github/CODEOWNERS
# API shared libraries
/packages/api-client/ @team-platform
/packages/auth/ @team-security
# Services
/services/user-service/ @team-identity
/services/payment-service/ @team-payments
/services/shipping-service/ @team-logistics
# Infrastructure
/infrastructure/ @team-sre
/scripts/deploy/ @team-sre
# Documentation
/docs/ @team-platform @team-tech-writers
When someone opens a PR touching packages/auth/, GitHub automatically requests
review from @team-security. This ensures the right people review changes
without manual routing.
For changes spanning multiple areas, multiple teams get requested automatically:
# PR changes files in three directories
- packages/auth/token-validator.ts
- services/user-service/auth-handler.ts
- infrastructure/auth-service.yaml
# Automatic review requests to:
- @team-security (for auth package)
- @team-identity (for user service)
- @team-sre (for infrastructure)
Affected Package Testing
Computing and testing only affected packages dramatically reduces CI time:
# Using Nx for affected testing test-affected: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # Need full history for affected analysis - name: Compute affected packages run: | npx nx affected:apps --base=origin/main --head=HEAD - name: Test affected packages run: | npx nx affected:test --base=origin/main --head=HEAD --parallel=3 - name: Build affected packages run: | npx nx affected:build --base=origin/main --head=HEAD --parallel=3
This approach only tests packages that changed or depend on changed packages. A feature branch that modifies one service might run 5% of tests instead of 100%, cutting CI time from hours to minutes.
Branch Naming with Service Prefixes
In monorepos, branch names should indicate which services they affect:
# Service-specific changes user-service/feature/profile-page payment-service/bugfix/currency-rounding # Shared library changes api-client/feature/request-retry auth/refactor/token-validation # Cross-cutting changes platform/feature/logging-improvements # Infrastructure changes infra/chore/update-terraform
This makes it immediately clear which teams care about each branch and helps with filtering CI jobs, automating deployments, and understanding the scope of changes.
Managing Dependencies Between Feature Branches
When features depend on each other, coordinating branches becomes critical.
Stacking Feature Branches
Sometimes Feature B depends on Feature A. Instead of waiting for A to merge, stack branches:
main
↓
feature/A (base: main)
↓
feature/B (base: feature/A)
Branch B builds on Branch A's changes. When A merges, rebase B onto main:
# After feature/A merges git checkout feature/B git rebase --onto main feature/A
This flattens the stack, removing A's commits (now in main) and leaving only B's commits.
Stacking lets dependent work proceed in parallel. Team A works on their feature, Team B works on their feature that needs A's changes, and both features progress simultaneously.
Feature Flag Coordination
For changes that require coordination across services, use feature flags:
// Service A: New behavior behind flag if (featureFlags.get('new-auth-flow')) { return await newAuthenticationFlow(request) } else { return await legacyAuthenticationFlow(request) } // Service B: Consumes new flow when enabled const authClient = new AuthClient({ useNewFlow: featureFlags.get('new-auth-flow') })
All services merge their feature branches with flags disabled. Once everything is deployed, enable the flag to activate the new behavior across all services simultaneously. This decouples deployment from feature activation.
Dependency Version Management
When updating shared library versions in a monorepo, use workspace dependencies:
// packages/user-service/package.json { "dependencies": { "@mycompany/api-client": "workspace:*" } }
The workspace:* protocol ensures services always use the current version of
shared libraries from the monorepo, eliminating version mismatch issues during
development.
Branch Protection at Scale
Monorepos need sophisticated branch protection rules to maintain quality without slowing teams down.
Required Checks Based on Changed Files
Run different checks for different parts of the monorepo:
# Only run user-service tests when user-service changes test-user-service: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Check for user-service changes id: filter uses: dorny/paths-filter@v2 with: filters: | user-service: - 'services/user-service/**' - name: Test user-service if: steps.filter.outputs.user-service == 'true' run: npm test --workspace=user-service
This prevents running unrelated tests. A documentation change doesn't need to run the payment service test suite.
Conditional Required Status Checks
GitHub allows marking checks as required only when they run:
Branch protection rules:
☑ Require status checks to pass
☑ Require status checks for changed files only
- test-user-service (required if runs)
- test-payment-service (required if runs)
- test-api-client (required if runs)
This means each PR only requires checks for the services it actually modifies.
Team-Specific Protection Rules
Different teams may need different rules. GitHub allows per-team bypass of certain protections:
main branch protection:
☑ Require pull request reviews (2 approvals)
☑ Allow specified actors to bypass (Platform Team)
release/* branches protection:
☑ Require pull request reviews (3 approvals)
☑ Require review from Code Owners
☑ No bypass allowed
This lets platform teams move quickly on infrastructure changes while maintaining stricter controls for releases.
Deployment Strategies for Monorepo Services
Deploying from feature branches in monorepos requires coordination across services.
Service-Specific Preview Environments
Deploy only changed services to preview environments:
deploy-preview: steps: - name: Determine changed services id: changes run: | services=$(npx nx affected:apps --plain --base=origin/main) echo "services=$services" >> $GITHUB_OUTPUT - name: Deploy affected services run: | for service in ${{ steps.changes.outputs.services }}; do echo "Deploying $service to preview" ./scripts/deploy-preview.sh $service ${{ github.head_ref }} done
This deploys only the services that changed, avoiding unnecessary deployments and reducing preview environment costs.
Canary Deployments Per Service
In monorepos with microservices, canary individual services rather than the entire system:
deploy-canary-user-service: if: contains(github.event.pull_request.labels.*.name, 'canary-user-service') steps: - name: Deploy user-service to 5% of production run: | ./scripts/deploy-canary.sh user-service 5% - name: Monitor for 1 hour run: | ./scripts/monitor-metrics.sh user-service 3600 - name: Promote or rollback run: | if ./scripts/check-health.sh user-service; then ./scripts/promote-canary.sh user-service else ./scripts/rollback-canary.sh user-service fi
This allows testing risky changes in production with minimal blast radius. Only users served by the canary see the new code, and automatic rollback prevents widespread issues.
Progressive Service Rollouts
For changes spanning multiple services, roll them out progressively:
Day 1: Deploy service-a (with feature flag off)
Day 2: Deploy service-b (with feature flag off)
Day 3: Deploy service-c (with feature flag off)
Day 4: Enable feature flag at 5%
Day 5: Enable feature flag at 25%
Day 6: Enable feature flag at 100%
This staged approach isolates deployment issues from feature issues and provides multiple rollback points.
Performance Optimization for Large Codebases
Monorepos require optimization to keep feature branch workflows fast.
Partial Clones and Sparse Checkouts
Reduce clone time with partial clones:
# Clone only recent history git clone --depth=1 https://github.com/company/monorepo # Sparse checkout: only checkout specific services git sparse-checkout init --cone git sparse-checkout set services/user-service packages/api-client
This dramatically reduces clone and checkout time for large repos. Developers working on one service don't need to fetch the entire history of every service.
Build Caching Across Branches
Share build caches between branches to speed up CI:
- name: Setup build cache uses: actions/cache@v3 with: path: | node_modules/.cache .nx/cache key: ${{ runner.os }}-build-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-build- ${{ runner.os }}-
The first feature branch builds everything and caches it. Subsequent branches restore from cache, building only what changed.
Distributed Task Execution
Tools like Nx Cloud distribute tasks across multiple machines:
- name: Setup Nx Cloud run: npx nx-cloud start-ci-run - name: Run affected tests run: npx nx affected:test --parallel=5 --ci
This spreads test execution across available agents, dramatically reducing total CI time for large test suites.
Communication and Coordination Patterns
Technical solutions only go so far. Monorepos require strong communication patterns.
RFC Process for Major Changes
For changes affecting multiple teams, write RFCs (Request for Comments) before creating feature branches:
# RFC: Migrate to new authentication library ## Summary Migrate from legacy-auth to modern-auth across all services. ## Affected Teams - @team-identity (owns 3 services) - @team-payments (owns 2 services) - @team-platform (owns auth library) ## Migration Plan 1. Update auth library interface (Platform team) 2. Update high-traffic services (Identity, Payments) 3. Update low-traffic services (all teams) 4. Deprecate legacy library ## Timeline - Weeks 1-2: RFC review and library updates - Weeks 3-6: Service migrations - Week 7: Final deprecation
This builds consensus before code is written, preventing surprises when large feature branches appear.
Slack Channels for Coordination
Create channels for coordinating complex changes:
#feat-auth-migration
- Status updates
- Blocker discussion
- Review requests
- Deployment coordination
This gives everyone working on related features a shared space for coordination.
Branch Status Dashboard
Build dashboards showing feature branch status:
Current Feature Branches by Team:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Team Identity:
feature/profile-redesign (7 days old, CI passing, needs review)
feature/email-verification (2 days old, CI passing, approved)
Team Payments:
feature/stripe-migration (14 days old, CI passing, blocked on infra)
bugfix/currency-rounding (1 day old, CI passing, needs review)
Team Platform:
feature/logging-v2 (21 days old, CI failing, needs rebase)
This visibility helps teams coordinate and identify branches that need attention.
The Connection to Better Monorepo Management
Feature branches in monorepos require more discipline and tooling than in smaller codebases, but the investment pays off. When done well, monorepos provide the benefits of code reuse, atomic cross-service changes, and unified tooling while maintaining the agility of feature branch development.
At Pull Panda, we work with teams managing codebases of all sizes. We've seen that successful monorepo development depends on providing clear context for reviewers. When reviewing a change that touches five services, reviewers need to understand the scope, see test results for all affected services, and evaluate changes in the context of the broader system. Feature branches that provide this context enable effective code review even at monorepo scale.
For more on feature branch fundamentals that apply regardless of repository size, see our complete guide to mastering feature branches. And to understand how CI/CD integration helps manage monorepo complexity, check out our article on feature branches and CI/CD.

