Skip to content

Application migration

Next.js Migration & Rescue

Pages Router to App Router, another framework to Next.js, or an inherited codebase nobody wants to touch - migrated incrementally, in production, without a freeze.

Two situations bring most teams here. Either an existing Next.js application has outgrown the Pages Router and the incremental path is not obvious, or an application built by someone else has become something the current team is afraid to deploy.

Both are tractable. Neither requires a rewrite.

Pages Router to App Router

The two routers interoperate. That is the whole strategy: a shared application where app/ and pages/ coexist, migrated leaf-first, with each route moving behind its own pull request and its own verification.

We start with low-traffic, low-risk routes to build the shared patterns - layouts, data access, error boundaries - then work up to the routes that matter. Data fetching moves from getServerSideProps into the component tree. Client state that only ever existed to compensate for the old rendering model usually disappears entirely.

The order is deliberate rather than alphabetical. The first routes to move are the ones that teach the team the new patterns at the lowest cost of being wrong, and the last are the ones where being wrong is expensive. By the time a checkout or a dashboard moves, the conventions it needs have already been proven on something that does not take revenue.

What usually takes the longest is not routing. It is the shared code that assumed the old model - a data hook that expected to run in the browser, a context provider wrapping the whole application because one component three levels down needed a value, an authentication check written for getServerSideProps. Each of those has a smaller replacement in the new model, and finding them is the actual work. The mechanics, in detail.

Another framework to Next.js

Create React App, Vite SPAs, Gatsby, and a long tail of bespoke webpack configurations. The mechanical part is routing and build configuration; the part that takes judgement is deciding which of the application's assumptions were framework artefacts and which were real requirements.

A single-page application moving to server rendering is the case with the most surprises in it, because everything in an SPA can assume a browser. window in a module body, a date formatted in local time, a client-side auth guard that was the only thing preventing access to a page - all of them work perfectly until the same code runs on a server. That migration has its own page, because it is enough work to be its own engagement.

Inherited codebases

Before we change anything, we read everything. Two weeks of orientation produces a dependency map, a rendering inventory of every route, a list of what is dead, and a ranked risk register. That document is yours regardless of what you decide next - it is the thing that makes the codebase governable again, and several clients have taken it in-house and done the work themselves.

We think that is a reasonable outcome.

The risk register is the part that changes conversations. "The codebase is a mess" is a feeling and cannot be scheduled against. "Four routes have no error boundary, the payment integration has no test, and two dependencies have known advisories and no upgrade path" is a list, and a list can be worked through in priority order by whoever is available. What that read produces.

What holds the migration together

URLs do not move. The migration is internal. Every address that ranked yesterday answers the same way tomorrow, and where one genuinely must change, it redirects permanently and the redirect is asserted in CI.

Every step is deployable. No long-lived migration branch. If the work stops for a quarter because your priorities changed, what is merged is coherent and what is left is a list rather than a conflict.

The old and the new do not both get to be right. Once a pattern is chosen it replaces the previous one on the routes that have moved, rather than accumulating beside it. A codebase carrying two conventions indefinitely is the state the migration was supposed to end.

Verification per route, not per release. Each moved route is checked for rendering mode, bundle size and behaviour before the next one starts.

When an audit is the better first step

If what you have is a suspicion rather than a decision - the application feels slow, or fragile, or expensive to change, and nobody can say exactly why - an audit answers that in two weeks and tells you whether migration is even the right response. Sometimes it is not: the router is fine and the problem is a rendering decision on four routes, which is a far smaller piece of work.

How it is scoped

The first deliverable is a map rather than a migration: which routes exist, what each one depends on, and which ones nobody can explain any more.

From it comes a phased scope with a route count and a price for each phase. The contract refers to that list, so the argument about what is in scope happens while it is still cheap. You can stop after any phase and keep a working application, and that property is what makes this survivable.

Then the phases, route by route, with both routers running until the last one moves.

What you receive

The application on the App Router, migrated route by route, each phase its own pull request against a route list agreed in advance. Nothing moves in a single change too large to review.

You also get the map made at the start: which routes exist, what each depends on, and which ones nobody has been able to explain. That document outlives the migration and is usually the first thing a new developer is handed.

Scope and terms

Engagement model
Fixed scope, agreed in writing before work starts. Not a day rate against an open backlog.
Price and timeline
Both are set per project, once the scope is. Quoted together, before anything is built.
What we need from you
One person who can approve decisions, and access to your repository and issue tracker.
Not included
Anything outside the agreed scope. It becomes its own scope rather than a variation order.
Third-party costs
Hosting, licences, API fees and SaaS subscriptions are contracted and paid by you.
Invoicing
Codefacture Yazılım A.Ş., Türkiye. EUR, USD or GBP by bank transfer, with no Turkish VAT on exported services.

Frequently asked questions

Do we have to stop shipping features during the migration?
No. The App Router and Pages Router run side by side in the same application, so we migrate route by route while your team keeps shipping. A feature freeze is a sign the migration was planned badly.
What if the existing code is genuinely bad?
Then we say so, with specifics and a cost comparison between incremental migration and a rebuild of the affected surface. We have no incentive to recommend the longer path, and a rewrite is usually the wrong answer.
Can you take over a project the previous team abandoned?
Yes. That is a meaningful share of our work. We start by reading, mapping and documenting before touching anything - how long that takes depends on the codebase, and you get that map whether or not you continue with us.
What happens to our SEO during a migration?
Nothing, if the URLs do not move. That is the first constraint we write down: the migration is internal, so every address that ranked yesterday answers the same way tomorrow. Where a URL genuinely has to change we redirect it permanently and verify the redirect in CI rather than trusting that somebody remembered.
Is there a point where you would tell us not to migrate?
Yes. If the application is stable, nobody is blocked by it, and no change is planned for the next year, the migration is cost without a return. The router you are on is not a defect by itself.
Call us+1 848 272 7583WhatsApp+90 850 308 5436Emailinfo@codefacture.comContact page