Engineering case study
Modernizing Birdeye's Frontend Platform
Led the phased modernization of Birdeye's legacy PHP/jQuery frontend into a scalable React platform without disrupting product delivery—enabling faster development, improved consistency, and long-term platform evolution.
Migrating from PHP/jQuery to React at enterprise scale—platform transformation across engineering, organization, and business delivery.
“The biggest challenge wasn't React—it was modernizing a live product without slowing 40+ engineers.”
Why the business funded this
Legacy PHP/jQuery architecture had become the primary bottleneck for feature velocity, frontend consistency, and engineering productivity. Modernizing the platform was essential to continue scaling product development on $100M+ ARR surfaces.
My role
Engineering Lead → Engineering Director
Team
40+ engineers across product teams
Duration
Multi-year platform modernization
Responsible for
- Led migration strategy
- Managed frontend / UI platform direction
- Worked with Product & Design
- Reviewed architecture
- Planned phased rollout
- Mentored engineers
- Hiring
- Stakeholder communication
- Delivery ownership
$100M+ ARR
Business scale
40+
Engineers
0
Rewrite freeze
Weekly
Delivery
PHP → React
Platform
React SPA
End state
Engineering timeline
- Discovery
- Architecture
- Prototype
- Shared components
- Migration
- Dual runtime
- Legacy removal
Company
Birdeye
From
PHP / jQuery
To
React SPA
Strategy
Incremental dual runtime
Products
Social · Insights · Campaigns
Live
birdeye.com
Business
Business impact
- Kept $100M+ ARR product delivery moving—no multi-quarter rewrite freeze
- Raised developer productivity with one shared React platform
- Faster onboarding onto a single frontend stack and pattern language
- Enabled independent team ownership of product surfaces
- Unlocked later UI/UX consistency across Social, Insights, and Campaigns
Why this problem mattered
Legacy PHP/jQuery had become the primary bottleneck for feature velocity, consistency, and productivity. Modernizing was essential to keep scaling product development.
We evaluated rewrite, wrap, or incremental migration. A full rewrite would have frozen delivery on revenue products. We chose incremental dual runtime so teams could keep shipping while replacing surfaces—one at a time.
Overview
Birdeye’s enterprise SaaS needed a frontend that could keep pace with product velocity. PHP/jQuery made UI slower to change, harder to share across Social, Insights, and Campaigns, and poorly suited to a modern SPA.
Goals
- Replace PHP/jQuery UI with a maintainable React SPA foundation
- Migrate without freezing the product roadmap
- Keep dual runtime explicit, time-boxed, and owned—not accidental forever
- Give product teams shared patterns and clearer UI ownership
- Improve UI iteration speed for enterprise customer-facing products
Architecture
Legacy PHP/jQuery did not jump straight to React. A shared component layer made dual runtime finishable; SPA journeys then shared the same API layer and services.
Shared components and contracts were the difference between temporary dual runtime and permanent fragmentation.
- Browser
- CDN
- Legacy PHP / jQuery
- Shared componentsPrimitives, patterns, standards
- React SPAMigrated product journeys
- API layer
- Backend services
Organization
Team organization
Not a single-squad rewrite. Success required Frontend, Backend, Platform, QA, Product, and Design shipping in parallel—coordination was the hard problem.
Engineering (40+) ├── Frontend / UI platform ├── Product engineering teams ├── Backend services ├── QA ├── Product └── Design
Engineering organization
Scaled to 40+ engineers across product teams
Product engineering teams
Owned customer-facing features on Social, Insights, Campaigns, and related surfaces
Frontend / UI platform
Architecture, shared components, migration standards, and dual-runtime guardrails
Backend services
API contracts and service ownership that both stacks depended on
QA
Regression coverage and release confidence for cutovers
Product & Design
Journey prioritization, UX consistency, and acceptance of React as the product surface
- Communication: migration status was framed as delivery continuity and cleanup progress—not “more React pages.”
- Releases: cutovers used controlled rollout and explicit rollback ownership.
- Ownership: every dual-runtime surface had a clear owner and a cleanup definition of done.
Migration
Migration phases
Phase 1
Component library
Shared UI primitives so migrated screens did not invent React differently every time.
Phase 2
Shared routing
Stable SPA boundaries and navigation contracts for dual-runtime coexistence.
Phase 3
Feature migration
Journey-by-journey cutover of customer-facing product surfaces.
Phase 4
Legacy removal
Retire PHP/jQuery once React journeys were stable—cleanup as a milestone, not a wish.
Engineering
Highlights
A typical surface migration
Discover legacy behavior → prototype React → controlled rollout → clean up PHP/jQuery once stable.
- Discovery — inventory PHP/jQuery behavior & risks
- Architecture — route, ownership, and contracts
- Prototype — prove React on a thin slice
- Migration — dual runtime by surfaceShared components where possible
- Rollout — controlled release + monitoring
- Cleanup — retire legacy once stable
Judgment
What didn't work
Big-bang migration
Problem
Would have blocked feature teams and frozen delivery on revenue products.
Decision
Abandoned after architecture review—blast radius was unacceptable.
Moved to
Incremental migration, shared components, dual runtime, explicit cleanup.
Why not rewrite everything at once?
Full rewrite: freezes delivery on $100M+ ARR products.
Thin wrap: looks modern, fixes nothing.
Incremental dual runtime: continuous releases while replacing surfaces. Temporary complexity was the price of uninterrupted value—and cleanup was a milestone, not a wish.
Judgment
Engineering decisions I was proud of
Shared component layer over a freeze rewrite
Instead of
Stopping product development to rewrite the app end-to-end.
Why
Revenue teams could not pause. Shared components let us migrate incrementally while converging on one UI platform.
Trade-off
Temporary dual-runtime complexity—and the discipline to finish cleanup.
Benefit
Weekly releases continued; React adoption compounded instead of fragmenting.
“Temporary complexity is a tool. Permanent complexity is a leadership failure.”
Key engineering decisions
Migration strategy
Problem
We needed a modern React platform without stopping feature delivery on live enterprise products.
Options
Option A — Full rewrite
Pros
- Clean end-state architecture
- No long-term dual stack
Cons
- Freezes product development
- High blast radius if wrong
- Hard to show incremental value
Rejected because: Delivery risk was unacceptable for enterprise customers and revenue product roadmaps.
Option B — Wrap legacy and move on
Pros
- Faster short-term cosmetics
- Less migration work upfront
Cons
- Doesn’t fix maintainability
- Hiring and velocity stay stuck on jQuery
Rejected because: It deferred the real problem and would recreate inconsistency under a new skin.
Option C — Incremental dual-runtime migration· Chosen
Pros
- Weekly releases continue
- Lower blast radius per cutover
- Learning compounds on real traffic
Cons
- Temporary PHP/jQuery + React coexistence
- Needs strict ownership and cleanup discipline
Chosen because: It protected customer value while still finishing the platform change.
Decision
Ship React journey by journey, keep delivery moving, retire legacy on evidence—not optimism.
Trade-offs
Temporary dual-runtime complexity for uninterrupted releases.
Result
Teams kept shipping Social, Insights, and Campaigns while the platform changed underneath.
Shared components during dual runtime
Problem
Without shared primitives, every migrated screen would invent React differently—and dual runtime would become permanent fragmentation.
Why
Shared components and standards so React adoption compounded instead of fragmenting.
Pros
- Lower rewrite cost per surface
- More coherent UI
- Path into later UI/UX consistency
Cons
- Upfront coordination
- Friction vs one-off screens
Trade-offs
Slower first migrations; faster later ones.
Result
A reusable foundation—not one-off React pages.
Release and rollback as architecture
Problem
Migrating customer-facing UI without a rollback story is not modernization—it’s gambling.
Why
Every cutover needed rollout, monitoring, compatibility, and a path back.
Pros
- Lower production risk
- Stakeholder confidence
- Cleanup after stability
Trade-offs
More process per cutover—worth it for enterprise trust.
Result
Dual runtime stayed intentional and finishable.
Challenges I had to solve
Challenge
Legacy PHP/jQuery couldn’t support velocity, consistency, or hiring as products multiplied.
Solution
React as target architecture; migrate surface by surface with shared components.
Result
A modern SPA foundation without a greenfield freeze.
Challenge
Dual runtime confuses ownership if contracts and cleanup rules are vague.
Solution
Explicit surface owners, shared boundaries, standards, and deliberate legacy retirement.
Result
React adoption compounded instead of fragmenting.
Challenge
Feature work, QA, and migration colliding across product teams.
Solution
Sequenced cutovers; release strategy part of engineering design.
Result
Modernization advanced alongside Social, Insights, and Campaigns.
Challenge
Architecture loses to feature pressure unless risk and communication stay visible.
Solution
Stakeholder communication, mentoring, and hiring—not just a slide deck.
Result
Modernization stayed funded, staffed, and finishable.
Delivery
Risk mitigation
| Risk | Mitigation |
|---|---|
| Broken releases | Feature flags |
| Regression | Shared components + standards |
| Downtime / freeze | Dual runtime |
| Slow rollout | Incremental migration |
| Forever dual-stack | Cleanup milestones + owners |
Feature flags
New React surfaces could be enabled gradually and disabled quickly without a full rollback drama.
Canary / controlled releases
Migrated journeys shipped behind controlled release patterns so blast radius stayed small.
Rollback strategy
Each cutover needed a defined path back if a React surface failed acceptance or production health checks.
Monitoring
Watched production signals during dual runtime so cleanup decisions were evidence-based.
Performance budgets
Migrated UI had to meet interaction and load expectations—not merely “render in React.”
Dual runtime
Treated dual runtime as explicit, owned, and time-boxed—with cleanup as a milestone.
Organization
Engineering leadership
Roughly as much leadership as architecture. Migrations fail from communication and ownership—not frameworks.
40+
Engineers
Multi-team
Product teams
Weekly
Architecture reviews
Cross-functional
Planning
“Incremental delivery mattered more than architectural purity.”
Weekly architecture reviews
Standards, shared components, and dual-runtime boundaries stayed visible.
Migration governance
Done meant React live, monitored, and legacy retired—or not done.
Cross-team planning
Sequenced cutovers so feature and migration work did not thrash.
Hiring & mentoring
Grew owners for React surfaces; standards compounded through reviews.
Stakeholder communication
Progress = delivery continuity and cleanup—not “more React pages.”
Product alignment
React journeys treated as the product surface, not a side quest.
Outcomes
Outcomes
Business outcomes
Feature velocity
No multi-quarter rewrite freeze on $100M+ ARR product lines
Product continuity
Weekly releases continued while the platform changed underneath
Follow-on leverage
Unlocked later UI/UX consistency across Social, Insights, Campaigns
Engineering outcomes
Platform
PHP/jQuery surfaces migrated onto a React SPA foundation
Architecture
Shared component layer + dual runtime, then cleanup to React-only
Delivery system
Controlled rollout, monitoring, and rollback as first-class design
Team outcomes
Ownership
Independent product teams could own React surfaces on shared standards
Onboarding
One frontend stack and pattern language instead of one-off jQuery screens
Leadership cadence
Architecture reviews, migration governance, hiring, and mentoring
Transformation
What changed after migration
| Before | After |
|---|---|
| PHP / jQuery | React platform |
| Inconsistent UI | Shared components |
| Slow feature delivery on legacy UI | Faster releases on a shared stack |
| Duplicate one-off screens | Reusable architecture |
| Hard-to-hire frontend stack | Modern React ownership model |
| Ambiguous dual-stack risk | Explicit dual runtime → cleanup |
Lessons learned
The hardest part wasn’t React—it was coordinating teams without slowing delivery.
- Write down why you rejected rewrite and wrap—not only what you chose.
- Dual runtime is fine; ambiguous ownership is not.
- Shared components make incremental migrations compound.
- Release and rollback are architecture decisions.
- Tie modernization to customer journeys so it stays finishable.
Leadership
Leadership takeaways
- Technology rarely causes migrations to fail. Communication does.
- Keeping Product, QA, Engineering, and Design aligned was as important as the React code itself.
- Temporary complexity is a tool. Permanent complexity is a leadership failure.
What I'd do differently
- I’d publish cutover and cleanup checklists earlier for every legacy surface.
- I’d invest even sooner in shared React primitives before more screens migrated.
- I’d pair migration progress more tightly with delivery dashboards stakeholders already trusted.
If I were starting today
Same incremental strategy—stronger platform earlier:
- Stronger module boundaries where team ownership matters
- Design tokens alongside shared components
- Automate migration checklists and regression coverage
- Storybook earlier for shared UI
- DORA-style signals on cutovers and cleanup
Biggest takeaway
Not a framework swap—
a judgment call to protect delivery while finishing the platform change.
Key takeaways
- Modernization succeeds because of execution, not frameworks.
- We optimized for incremental delivery, alignment, compatibility, and adoption.
- Temporary complexity is a tool. Permanent complexity is a leadership failure.
Final reflection
Looking back, the migration succeeded not because React was a better framework, but because we optimized for incremental delivery, organizational alignment, and long-term maintainability. The technical solution was only one part of the transformation—the execution strategy mattered just as much.
← All projects