BuildWithMatija
  1. Home
  2. Blog
  3. Next.js
  4. 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…

7th September 2026·Updated on:23rd September 2026·MŽMatija Žiberna·
Next.js
How to Take Over a Frontend: 4-Step Production Framework

⚡ 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.

No spam. Unsubscribe anytime.

📄View markdown version
0

Frequently Asked Questions

Comments

About the author

Matija Žiberna

Matija Žiberna

Full-stack developer, co-founder

AboutResume

Self-taught full-stack developer sharing lessons from building software and startups.

I'm Matija Žiberna, a self-taught full-stack developer and co-founder passionate about building products, writing clean code, and figuring out how to turn ideas into businesses. I write about web development with Next.js, lessons from entrepreneurship, and the journey of learning by doing. My goal is to provide value through code—whether it's through tools, content, or real-world software.

Contents

  • The three tempting approaches
  • 1. Rewrite it
  • 2. Keep everything exactly as it is
  • 3. Refactor everything before integration
  • Preserve first. Prove parity. Integrate vertically. Harden on touch.
  • Step 1: Understand what actually exists
  • Separate design quality from production architecture
  • First create a trustworthy baseline
  • Then integrate vertically
  • Do not integrate the entire site before hardening anything
  • What Hardening Actually Means
  • 1. Component differences become explicit contracts
  • 2. Local component styling becomes locally scoped
  • 3. Mature primitives replace difficult interaction mechanics selectively
  • The business side of the architecture matters
  • A practical framework
  • 1. Design system
  • 2. Component contracts
  • 3. Styling ownership
  • 4. Interaction complexity
  • 5. Data boundaries
  • 6. Production evidence
  • 7. Migration risk
  • The resulting architecture
  • The rule I would keep
On this page:
  • The three tempting approaches
  • Preserve first. Prove parity. Integrate vertically. Harden on touch.
  • Separate design quality from production architecture
  • First create a trustworthy baseline
  • Then integrate vertically
Build with Matija Logo

Build with Matija

Complex B2B websites, headless CMS platforms, AI workflows, and internal systems designed and built with Next.js and Payload CMS.

Services

  • B2B Website Development
  • CMS Architecture Review & Platform Blueprint
  • Next.js + Payload Advisory
  • AI Integration & Implementation

Resources

  • CMS Hub
  • B2B Website Strategy
  • E-commerce Hub
  • Blog
  • Case Studies
  • About Matija

Payload CMS

  • Payload CMS Developer
  • Payload CMS Migration
  • Payload CMS Demos
  • All Payload CMS Resources

Discuss your project

Planning a rebuild, migration, application, workflow change, or platform decision? Start with the business problem and the system behind it.

Book a discovery callContact me →
© 2026Build with Matija•Alle Rechte vorbehalten•Datenschutzerklärung•Nutzungsbedingungen
BuildWithMatija
Get In Touch

Have you ever had to integrate two codebases built by different developers working in parallel?

On a recent project, I was setting up the Payload CMS backend architecture and content models, while another developer was separately building out the Next.js frontend in an isolated repository. When the time came to merge the two sides together, the frontend was already substantial: custom components, design tokens, mock layouts, and a massive stylesheet were fully in place.

My initial developer instinct was predictable: maybe we should clean this up and rebuild it from scratch directly against our CMS schema.

Following that instinct would have thrown away weeks of design exploration, duplicated existing work, and delayed the production timeline. The real challenge wasn't proving what I would have done differently; it was figuring out how to preserve the valuable design and UI work that already existed, integrate it cleanly with Payload, and establish a maintainable production architecture without burning the schedule on unnecessary rewrites.

That reframing changed our entire approach. Here is the 4-step framework we used to take over an existing frontend safely.

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.

Something conceptually like this:

css
.hero .product-card__title {
  color: white;
}

.recipe-feature .product-card__media {
  height: 140px;
}

That is more significant than the size of the stylesheet.

Why?

Because another part of the application needs knowledge of how ProductCard is implemented internally.

The requirement exists without being visible in the component API.

The CSS is effectively telling us:

ProductCard actually has an inverse version.

and:

ProductCard actually has a compact version.

Those are hidden component requirements.

The better API might be:

tsx
<ProductCard
  tone="inverse"
  density="compact"
/>

Now the component owns what those concepts mean.

That was an important shift in the diagnosis: some component contracts stayed implicit when they should have been explicit.

Separate design quality from production architecture

This is particularly important when the frontend was produced by a designer or design-focused developer.

A prototype can do its job extremely well without being structured exactly like the production application should be.

Those are different optimization targets.

During design exploration, speed of iteration matters.

You may need to try multiple card treatments, hero layouts or responsive behaviours quickly.

Production has different requirements:

  • predictable ownership
  • explicit component interfaces
  • accessibility guarantees
  • maintainability
  • testing
  • CMS integration
  • long-term change safety

The original work simply reflects an earlier phase of the implementation.

As the person taking over the system, the job is to preserve the design intent while establishing the boundaries production needs.

First create a trustworthy baseline

This was probably the most important implementation decision.

Before restructuring the frontend, bring it into the actual production repository and make it work.

Copy the components.

Copy the assets.

Copy the design tokens.

Copy the styling required to reproduce the approved design.

Resolve framework differences and dependencies.

Resist the temptation to improve everything during the move.

The first milestone should be boring:

Can the production repository reproduce the existing frontend?

This gives you a known-good baseline.

You want:

text
Existing prototype
       ≈
Production repository using static/mock data

before you start changing its architecture.

Why?

Because otherwise you may simultaneously be:

  • moving repositories
  • changing styling
  • introducing a primitive library
  • changing component APIs
  • replacing mock data
  • integrating a CMS
  • changing routing

When something breaks visually, you no longer know why.

A faithful baseline turns future refactoring into controlled change: there is always a clear before-and-after to check any regression against.

Then integrate vertically

Once the frontend exists correctly in the production application, the real work can begin.

In this project, that means connecting it to Payload CMS.

Payload concerns should not spread throughout the presentation components.

The preferred architecture is:

text
Payload
   ↓
query / resolver / view model
   ↓
presentation component

For example:

text
Payload Hero configuration
        ↓
Hero resolver
        ↓
<Hero />

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:

css
.dark-section {
  --foreground: white;
  --surface: #123;
}

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.

3. Mature primitives replace difficult interaction mechanics selectively

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.

Comments