How to Take Over a Frontend: 4-Step Production Framework
How to Take Over a Frontend: 4-Step Production Framework
A practical framework to preserve design, prove parity, and integrate prototypes into production with Next.js and…
·Updated on:··
⚡ Next.js Implementation Guides
In-depth Next.js guides covering App Router, RSC, ISR, and deployment. Get code examples, optimization checklists, and prompts to accelerate development.
Have you ever opened a frontend repository, looked at the architecture, and immediately started thinking about everything you would have done differently?
Maybe there is a 20,000-line global stylesheet.
Maybe most of the components are custom.
Maybe Tailwind is installed but barely used.
Maybe dialogs, drawers and autocomplete controls contain their own accessibility logic.
And perhaps your first reaction is:
We should clean this up before we build on top of it.
That instinct is common, and following it immediately can derail a client implementation before it starts.
I recently had to make exactly this decision while taking over an existing Next.js frontend that was going to become the presentation layer for a new Payload CMS implementation.
The frontend had been built primarily to establish the design and user experience, and it looked good, with a substantial component system already in place. That system had grown out of early design exploration, before the production architecture had been decided.
The real question became how to preserve the value that already existed while creating a production architecture another team could safely maintain.
That reframing changed the entire approach.
The three tempting approaches
When you inherit an existing frontend, there are usually three obvious options.
1. Rewrite it
Replace the styling architecture.
Introduce your preferred component library.
Standardize everything.
This gives you the cleanest theoretical starting point, and it carries the most risk of the three.
You are rebuilding working visual behaviour, introducing regressions, consuming implementation budget and potentially invalidating months of design iteration.
You may end up with a cleaner codebase that doesn't guarantee you made the right business decision.
2. Keep everything exactly as it is
The opposite approach is to treat the existing frontend as finished.
Connect the CMS, replace mock data with real data and ship it.
This minimizes initial effort, and it means the production application ends up sitting directly on any hidden component dependencies, duplicated interaction logic, or fragile styling relationships already in place.
Once the CMS, business logic and additional features start depending on those components, fixing them becomes considerably harder.
3. Refactor everything before integration
This often feels like the responsible compromise.
Keep the design, and clean the architecture before connecting anything to the real system.
The problem is that you are refactoring without production pressure telling you which abstractions actually matter.
You may spend days improving components that barely appear in the final application while the real implementation is still waiting to begin.
That led me to a fourth approach.
Preserve first. Prove parity. Integrate vertically. Harden on touch.
This became the guiding rule:
Preserve first. Prove parity. Integrate vertically. Harden on touch.
Two instincts are common here: treat the frontend as disposable prototype code, or treat it as an untouchable finished product. Both skip a step. What matters first is understanding the useful parts of the existing system.
The sequence matters.
Step 1: Understand what actually exists
My initial impression came from one very visible number: roughly 20,000 lines of CSS in a global stylesheet.
That number sounds alarming on its own, and line count alone rarely tells you whether a frontend is architecturally broken.
A deeper audit showed considerably more structure:
a design-token system
consistent naming conventions
reusable UI components
Storybook documentation
accessibility checks
Floating UI in some interaction-heavy components
existing component variants
a meaningful shared UI layer
There was real engineering structure there, and it came with one important weakness.
Some features were reaching into shared components and styling their internal elements from outside.
The existing design component keeps functioning purely as the presentation component, regardless of which CMS sits behind it.
Payload owns content and configuration.
The mapping layer translates that content into the stable props the component expects.
This matters because it keeps two responsibilities separate:
CMS structure
and
visual presentation
The same principle applies regardless of whether the CMS is Payload, Contentful, Sanity or something else.
Do not integrate the entire site before hardening anything
There is another trap here.
You could establish visual parity, connect every page to the CMS and postpone all architectural cleanup until the end.
I would not do that either.
By then, the entire production application depends on the old contracts.
Work in vertical slices.
Take one real part of the application.
For example:
text
Payload content model
↓
query + mapping
↓
Blog template
↓
existing cards/components
↓
visual validation
↓
targeted component hardening
Then repeat for the next area.
Every time a component enters a real production path, inspect it.
That is what I mean by:
Harden on touch.
Do not pause the whole project for a massive cleanup, and do not continue building new dependencies on weaknesses you already understand.
What Hardening Actually Means
In this particular frontend, we eventually made three separate architectural decisions.
1. Component differences become explicit contracts
If a component genuinely has multiple visual modes, expose them deliberately.
For example:
tsx
<HeroProductCard
tone="dark"
density="compact"
/>
This keeps the variation inside the component's own API and prevents several unrelated parents from reaching in to override its internal selectors.
Some differences work better as section-level context than as individual props. When an entire section creates a visual context, semantic CSS variables can carry it:
A parent should be allowed to determine where a card sits in a grid, without needing to know how that card's internal title selector is named.
2. Local component styling becomes locally scoped
CSS Modules made sense once a component had a clear public API: its internal styling could then belong entirely to that component.
Something like:
text
ProductCard.tsx
ProductCard.module.css
with:
css
.root {}
.title {}
.media {}
.actions {}
The important detail is when to make that change.
Do not take a heavily coupled component, put all of its classes inside a CSS Module and celebrate that the CSS is now scoped.
You may simply have broken every external dependency.
First understand why those dependencies exist.
Turn legitimate differences into variants, context or parent layout.
Then scope the internals.
CSS Modules work best as one of the tools that enforce a component boundary once that boundary has been properly defined and its dependencies understood.
The frontend also had custom components for things like dialogs, drawers, autocomplete and tabs.
Some of them were already fairly sophisticated.
The real question was which interaction logic we actually wanted to own for the next several years:
Consider everything involved in a good dialog:
focus trapping
focus restoration
Escape handling
nested overlays
ARIA relationships
outside dismissal
keyboard behaviour
Or a combobox:
listbox semantics
arrow-key navigation
active descendants
selection state
focus transitions
screen-reader behaviour
Implementing all of that is possible, and every piece of custom interaction behaviour then becomes something the team needs to test and maintain going forward.
So the decision was to keep the existing component layer and visual design while using Base UI underneath selected interaction-heavy components.
Conceptually:
text
Feature
↓
Existing Modal
↓
Base UI Dialog behaviour
↓
Existing styling and animation
The application still consumes:
tsx
<Modal />
That boundary matters.
Base UI becomes an implementation detail.
And we do not force it onto everything.
A normal button can remain a button.
A text input can remain an input.
Existing Floating UI functionality can remain where it already solves the problem well.
The goal is reducing the amount of difficult interaction machinery the team needs to own.
The business side of the architecture matters
There is a broader lesson here for service businesses.
Technical implementation means balancing multiple factors alongside architectural cleanliness:
the value of existing work
implementation budget
delivery speed
regression risk
maintainability
stakeholder expectations
team capability
future ownership
A full rewrite may be cleaner and still be the wrong decision for the business.
Likewise, accepting all existing implementation choices because "the design already works" can save money today and quietly create a much larger maintenance problem later.
The value of an implementation partner is partly in navigating that middle ground.
You need to be able to say:
This part is good and should stay.
This part looks unusual, and it is not actually a problem.
This part creates production risk and should change.
This part should change, just not yet.
Those are very different conclusions.
A practical framework
When I inherit an existing frontend now, I would evaluate it in this order.
1. Design system
Is there a coherent token system?
Are visual patterns consistent?
Are components actually reused?
2. Component contracts
Do components own their variations?
Or do other parts of the application reach into their internals?
3. Styling ownership
Can you tell which component owns which styles?
Are overrides intentional or merely accumulated?
4. Interaction complexity
Which custom components own difficult keyboard, focus and accessibility behaviour?
Is that behaviour adequately tested?
5. Data boundaries
Are presentation components tied directly to mock data, APIs or CMS document shapes?
Can a mapping layer isolate those concerns?
6. Production evidence
Which components are actually needed by the real site?
Which abstractions exist mainly because of prototype experimentation?
7. Migration risk
Can you create a working baseline before changing architecture?
If not, every refactor becomes harder to validate.
The resulting architecture
For this project, the direction eventually became:
text
Existing design system
tokens / visual language / animations
↓
Existing branded components
↓
Explicit component APIs
↓
CSS Modules for locally owned styles
+
Base UI for selected complex interaction behaviour
+
Native HTML / Floating UI where appropriate
↓
Payload through a separate data-mapping layer
The specific technologies matter less here than the decision process behind them — another project could easily reach a different technical answer.
These were the questions that actually drove the decisions, ahead of any individual tool preference:
What already works?
Where are the actual dependencies?
Which risks become more expensive once production starts depending on them?
What is the least disruptive way to create stronger boundaries?
Starting from questions like these is what produces much better implementation decisions.
The rule I would keep
The simplest version is still the one I would use to guide the work:
Preserve first. Prove parity. Integrate vertically. Harden on touch.
There is a middle ground between blindly preserving someone else's frontend and rewriting it into your preferred stack, and it is usually the more useful path.
Understand the system.
Preserve the value.
Make the hidden contracts explicit.
Replace difficult machinery where mature solutions are genuinely better.
And let real production requirements tell you which parts deserve your attention first.
That is usually a better use of both engineering effort and the client's budget.