What I Learned Rewriting a React Codebase
When is a rewrite the right call? Lessons from rebuilding Gigson's frontend from scratch.
What I Learned Rewriting a React Codebase
Most rewrites are a mistake. The code you're replacing exists for a reason. The bugs you hate? They're covering edge cases you don't know about yet.
I rewrote Gigson's frontend anyway. Here's why it was the right call, and what I'd do differently.
The State of Things
When I joined Gigson, the frontend had:
- A 1.2MB main bundle (uncompressed)
- 15+ seconds initial load on African mobile networks
- Components that were 2000+ lines long
- No code splitting whatsoever
- A mix of class components, hooks, and... jQuery (yes, really)
The codebase had been touched by 8+ developers over 3 years. Each had their own style. None had time to clean up after themselves.
Why Rewrite Instead of Refactor?
I tried refactoring first. Spent two weeks extracting components, adding TypeScript, setting up better tooling.
Then I hit a wall.
The data flow was fundamentally broken. Props were drilled 6 levels deep, but also mutated along the way. State was stored in the URL, localStorage, Redux, and component state. Sometimes all four for the same piece of data.
Refactoring would mean fixing every component simultaneously. That's not a refactor; that's a rewrite with extra steps.
How I Did It
1. Document Everything First
Before writing new code, I mapped:
- Every API endpoint the frontend called
- Every user flow (with screenshots)
- Every "weird" behavior that was actually intentional
This took a week. It saved months.
2. New Shell, Gradual Migration
I built the new app structure alongside the old one:
/src
/legacy # Old components, untouched
/app # New App Router pages
/components # New component library
Pages were migrated one at a time. The legacy code stayed working until each page was fully replaced.
3. Performance as Architecture
Every architectural decision was filtered through: "What does this do to bundle size?"
- Route-based code splitting: Each page loads only what it needs
- Component lazy loading: Heavy components (rich text editors, charts) load on demand
- Progressive font loading: No render blocking
- Image optimization: Next.js Image with blur placeholders
The Results
| Metric | Before | After | | ------------------- | ------ | ----- | | Bundle size | 1.2MB | 480KB | | Initial load | 15s | 3s | | Lighthouse perf | 34 | 89 | | Time to Interactive | 18s | 4s |
60% smaller. 40% faster. The contract keeps extending.
What I'd Do Differently
1. Start with the data layer
I rebuilt the UI first and the data layer second. This meant redoing work when I realized the API response shapes didn't match my component designs.
Next time: Build the API client and TypeScript types first. Let the data shape drive the components.
2. Set up visual regression testing early
I broke things. Subtle things: button alignments, hover states, responsive breakpoints. I only caught them through manual testing.
Automated visual regression (Chromatic, Percy) would have caught these immediately.
3. Get stakeholder buy-in for the dark period
There was a 2-week period where both systems existed but neither was complete. Stakeholders got nervous. I could have prepared them better.
When to Rewrite
A rewrite makes sense when:
- The foundation is broken (not just the walls)
- You have time (minimum 2-3 months for a significant codebase)
- You can maintain the old system while building the new one
- Stakeholders understand the risk
If any of these are false, refactor instead.
Gigson is a talent recruitment platform connecting African developers with international companies. You can see the result at gigson.co.