Next.js as an in­te­gra­tion lay­er for lega­cy sys­tems: mod­ernise with­out the big bang

SAP, TYPO3, Sales­force and age­ing ERP sys­tems aren't go­ing any­where. They still have to talk to mod­ern fron­tends. How Next.js works as an in­te­gra­tion lay­er and Back­end for Fron­tend to de­cou­ple lega­cy step by step, what holds the pat­tern up, and where it tips over.
10 min readMatthias RadscheitMatthias Radscheit
Happycodingen-US

TL;DR

A Next.js integration layer for legacy systems sits in front of old backends through Route Handlers: it aggregates SAP, ERP and CMS behind a modern frontend without replacing them. That makes Strangler Fig modernisation far more realistic than any big-bang migration, as long as the layer never accumulates business logic and turns into a distributed monolith.

  • Next.js doesn't replace legacy, it wraps it: Route Handlers aggregate several data sources behind one frontend and hide an old SAP, ERP or CMS system behind a modern API layer.
  • The Strangler Fig pattern makes modernisation incremental. Feature by feature moves from the old system into the new layer, instead of betting everything on a big-bang replacement.
  • For pure backend-to-backend integration with no frontend tie, a dedicated platform (Kong, MuleSoft, a backend of your own) is usually the cleaner, language-agnostic choice over a Next.js BFF.
  • The layer adds a network hop: more latency, another deploy, one more point of failure. Server Components should fetch straight from the source, not via your own Route Handler.
  • If the layer accumulates business logic and chains synchronously into SAP, Salesforce and ERP, you get a distributed monolith, harder to operate than what it was meant to replace.

SAP is­n't go­ing away. TYPO3 is­n't go­ing away. The ERP that has car­ried the or­der log­ic since 2009 is­n't go­ing away ei­ther. These sys­tems aren't a prob­lem you solve, they're the re­al­i­ty you work in. The ques­tion is nev­er how to re­place them. It's how to put a mod­ern fron­tend in front of them with­out dis­ap­pear­ing into a three-year mi­gra­tion.

That is where Next.js gets in­ter­est­ing, and not in the role it's usu­al­ly sold for. Not as a ren­der­er for the fast mar­ket­ing site, but as a lay­er be­tween the brows­er and the back­end zoo. Through Route Han­dlers, Next.js ag­gre­gates sev­er­al data sources, nor­malis­es them, and hands the fron­tend ex­act­ly what it needs. It be­comes an in­te­gra­tion lay­er in front of the back­end, not a re­place­ment for it. That sounds un­spec­tac­u­lar. It is also the rea­son mod­erni­sa­tion be­comes re­al­is­tic at all in a lot of or­gan­i­sa­tions.

Why a Next.js in­te­gra­tion lay­er for lega­cy sys­tems beats what big-bang re­place­ments promise

The most ex­pen­sive de­ci­sion in lega­cy mod­erni­sa­tion is to re­place the old sys­tem in one go. It sounds clean: a new sys­tem, one mi­gra­tion, a cut-over date, and af­ter that every­thing is bet­ter. In prac­tice it's a high-stakes bet with poor odds. For the en­tire build the project de­liv­ers no val­ue, the old sys­tem keeps run­ning in par­al­lel, and on go-live day you find out whether two years of work hold up or not. With a SAP Com­merce stack, where the im­ple­men­ta­tion moves quick­ly into the high six-fig­ure to sev­en-fig­ure range ac­cord­ing to part­ner es­ti­mates (those are fig­ures from a mi­gra­tion provider, not an of­fi­cial price list, so read them with the ap­pro­pri­ate cau­tion), that bet sim­ply is­n't de­fen­si­ble.

The al­ter­na­tive is a Next.js in­te­gra­tion lay­er in front of the lega­cy es­tate that breaks the risk apart. Mar­tin Fowler coined the im­age of the Stran­gler Fig for this: the fig that grows around a tree and re­places it piece by piece un­til it stands on its own. In prac­tice that means small ad­di­tions along­side the old sys­tem, tak­ing over be­hav­iour fea­ture by fea­ture, the lega­cy shrink­ing while the new thing grows. Fowler is hon­est about it. The pat­tern does­n't make the ex­er­cise *easy*, and it de­mands ac­cept­ing a tran­si­tion­al ar­chi­tec­ture that looks ugly for a while. But it de­liv­ers val­ue from week one, and no sin­gle day de­cides suc­cess or fail­ure. Mi­crosoft de­scribes the same Stran­gler Fig mech­a­nism as a fa­cade that in­ter­cepts re­quests and, de­pend­ing on progress, routes them to the lega­cy or to the new ser­vice.

The fig needs a lay­er that owns this rout­ing. A place where it gets de­cid­ed: this data still comes from SAP, that data al­ready from the new ser­vice, and the fron­tend no­tices none of it. That place can be Next.js.

What Route Han­dlers do as an in­te­gra­tion lay­er

Tech­ni­cal­ly the pat­tern is un­spec­tac­u­lar, and that's a good thing. Next.js doc­u­ments the Back­end for Fron­tend of­fi­cial­ly, through Route Han­dlers, the proxy con­ven­tion and, in the Pages Router, API Routes. A Route Han­dler takes a re­quest from your own fron­tend, calls the ERP, the CMS and maybe a pric­ing ser­vice in the back­ground, stitch­es the re­spons­es to­geth­er, strips out the noise, and re­turns a clean an­swer tai­lored to the UI. The in­ter­nal sys­tems stay in­vis­i­ble. The fron­tend nev­er talks to SAP di­rect­ly. It talks to your lay­er.

Sam New­man, who coined the BFF term, frames the prin­ci­ple as "one ex­pe­ri­ence, one BFF": the lay­er be­longs to the fron­tend team and is tai­lored to ex­act­ly one ex­pe­ri­ence. It is­n't a uni­ver­sal gate­way, it's the be­spoke adapter be­tween one spe­cif­ic sur­face and the messy back­end world be­hind it. That's the strength, and at the same time the lim­it I'll come back to.

The of­fi­cial doc­u­men­ta­tion is re­mark­ably hon­est on one point, and de­ci­sion-mak­ers should com­mit this sen­tence to mem­o­ry: the back­end ca­pa­bil­i­ties of Next.js are not a full back­end re­place­ment, they're an API lay­er. That is­n't a weak­ness, it's the cor­rect self-as­sess­ment. Us­ing Next.js as a BFF does­n't re­place the sys­tem of record. It de­cou­ples the fron­tend from its quirks. The or­der log­ic stays in the ERP, where it be­longs. The lay­er only makes it con­sum­able.

Com­bined with the ren­der­ing me­chan­ics of Next.js, this be­comes more than a proxy. Serv­er Com­po­nents cut the JavaScript shipped to the brows­er, and In­cre­men­tal Sta­t­ic Re­gen­er­a­tion serves pre-ren­dered pages from cache for most re­quests, which gen­uine­ly low­ers the load on the lega­cy sys­tems be­hind it. An old ERP that had to an­swer every prod­uct page live buck­les un­der traf­fic. Be­hind an ISR lay­er it only an­swers the cache reval­i­da­tions. How far re­sponse times feed through to con­ver­sion in the end I've cov­ered at length else­where: the link be­tween per­for­mance and rev­enue is­n't a sideshow, it's of­ten the ac­tu­al busi­ness case for the whole ex­er­cise.

Which lega­cy sys­tems you can de­cou­ple this way

The pat­tern is­n't lim­it­ed to e-com­merce, even if SAP Com­merce gives the text­book ex­am­ple. Its Com­pos­able Store­front is an An­gu­lar fron­tend that speaks ex­clu­sive­ly through the OCC REST API. Teams that don't like that fron­tend, and many don't, be­cause it de­mands Java/Spring *and* An­gu­lar com­pe­tence in-house, can con­sume the same REST API from a Next.js fron­tend in­stead. The com­merce en­gine stays SAP, the ex­pe­ri­ence be­comes a dif­fer­ent one. I've made the broad­er case for Next.js and Medusa over SAP Com­merce and An­gu­lar sep­a­rate­ly.

The same holds for con­tent. An old TYPO3 or a CMS past its prime serves con­tent through an API, and Next.js ren­ders it in a mod­ern shell. At its core this is the head­less ap­proach with Word­Press and Next.js, ex­cept the "head­less" is bolt­ed on af­ter the fact here rather than planned from the start. Any­one re­con­sid­er­ing CMS strat­e­gy in gen­er­al should un­der­stand the dif­fer­ence be­tween a CMS and a DXP be­fore build­ing the lay­er, be­cause the lay­er's ar­chi­tec­ture looks dif­fer­ent de­pend­ing on the an­swer. And any­one still de­cid­ing whether TYPO3 should re­main the ba­sis at all will find my ar­gu­ments against TYPO3 in 2026 else­where.

Sales­force, Mi­crosoft Dy­nam­ics ERP, in-house sys­tems from the noughties: all of them have an API to­day or can have one put in front with man­age­able ef­fort. The in­te­gra­tion lay­er ag­gre­gates them. The cus­tomer sees one sur­face pulling data from three sys­tems and no­tices noth­ing of the frag­men­ta­tion be­hind it. That's the real val­ue. The or­gan­i­sa­tion­al and tech­ni­cal splin­ter­ing in the back­end gets hid­den from the user with­out hav­ing to be re­solved first. The re­solv­ing can come af­ter­wards, fea­ture by fea­ture, on the Stran­gler Fig ca­dence.

When a real in­te­gra­tion plat­form is the bet­ter choice

Here I have to ar­gue against my own the­sis, oth­er­wise the piece would be dis­hon­est. A Next.js BFF is an in­te­gra­tion lay­er *for a fron­tend*. The mo­ment the task is pri­mar­i­ly in­te­gra­tion with no fron­tend tie, back­end talk­ing to back­end, sys­tem sync­ing with sys­tem, data flow­ing be­tween ERP, CRM and data ware­house, a fron­tend frame­work is the wrong tool.

For those cas­es there are ded­i­cat­ed in­te­gra­tion plat­forms: an API gate­way like Kong, in­te­gra­tion mid­dle­ware like Mule­Soft, or a back­end of your own in the lan­guage you pre­fer. These solve ag­gre­ga­tion cen­tral­ly, lan­guage-ag­nos­ti­cal­ly and for any num­ber of con­sumers. They bring rate lim­it­ing, pro­to­col trans­for­ma­tion, ob­serv­abil­i­ty and au­then­ti­ca­tion as plat­form fea­tures, not as hand-rolled Route Han­dler code. When five dif­fer­ent con­sumers need the same ag­gre­gat­ed data, a cen­tral gate­way beats five BFFs du­pli­cat­ing the same log­ic, and New­man names code du­pli­ca­tion be­tween BFFs ex­plic­it­ly as one of the two main risks of the pat­tern.

The hon­est rule of thumb: if the ag­gre­ga­tion has a di­rect, spe­cif­ic fron­tend tie, the Next.js BFF is clos­er to the prob­lem and faster to build, be­cause it de­liv­ers ex­act­ly the data in ex­act­ly the shape that one sur­face needs. If the ag­gre­ga­tion is a gener­ic back­end job, it be­longs on an in­te­gra­tion plat­form. Re­pur­pose a fron­tend frame­work into an en­ter­prise in­te­gra­tion bus and you build your­self a prob­lem you'll pay to take apart lat­er.

The un­com­fort­able part: the lay­er adds a hop

Every in­te­gra­tion lay­er has a price, and it's qui­et­ly left out of the pitch. It's an ex­tra net­work hop. More la­ten­cy, an­oth­er de­ploy, an­oth­er point of fail­ure. The Next.js doc­u­men­ta­tion says this it­self with un­usu­al clar­i­ty: for on-de­mand Serv­er Com­po­nents, fetch­ing through your own Route Han­dlers is slow­er, be­cause an ex­tra HTTP round trip sits in the mid­dle. Serv­er Com­po­nents should fetch straight from the source, not take the de­tour through your own API lay­er. Ig­nore that and you build in la­ten­cy no­body asked for.

It gets worse when the lay­er starts to ac­cu­mu­late busi­ness log­ic. A BFF that chains syn­chro­nous­ly into SAP, then Sales­force, then the ERP, and waits for each re­sponse be­fore mak­ing the next call, de­gen­er­ates into a dis­trib­uted mono­lith: sep­a­rate­ly de­ployed but tight­ly cou­pled ser­vices with syn­chro­nous chat­ty calls, la­ten­cy chains and cas­cad­ing fail­ures. If the ERP goes down, the whole page goes down. That's hard­er to op­er­ate than the mono­lith the lay­er was meant to re­place. You've bought the down­sides of dis­tri­b­u­tion with­out any of the up­sides.

And even when every­thing stays clean, the lim­its of the lega­cy sys­tems don't dis­ap­pear, they get pa­pered over. An ERP that can't do real-time stock lev­els still can't be­hind an el­e­gant Next.js lay­er. The caching that low­ers the load is at the same time the mech­a­nism that serves stale data. Self-host­ed, ISR caching only works au­to­mat­i­cal­ly with *one* next start in­stance on a per­sis­tent disk; the mo­ment you go mul­ti-in­stance or CDN, you need a cus­tom cache han­dler, for ex­am­ple on Re­dis, with tag co­or­di­na­tion across mul­ti­ple nodes. That's the gen­uine­ly hard part of this pat­tern, and it gets un­der­es­ti­mat­ed be­cause the hap­py path looks so sim­ple. If you'd rather not run this your­self, the trade-offs of Next.js on Het­zn­er ver­sus Ver­cel de­cide a lot of it.

We've stood this lay­er up self-host­ed sev­er­al times, on Het­zn­er, via Coo­lify and Dock­er, with out­put: 's­tand­alone'. En­try sits at around 20 to 50 eu­ros a month; a high­ly avail­able vari­ant with re­dun­dant in­stances and a sep­a­rate data­base is more like 150 to 400 eu­ros. A clean first im­ple­men­ta­tion of such a stack we bud­get at 5,000 to 20,000 eu­ros one-off, with on­go­ing op­er­a­tion at two to five hours a month. As of mid-2026, treat those as our own part­ner fig­ures and ver­i­fy against your own con­text. That's all man­age­able, as long as the lay­er stays a lay­er and does­n't be­come a sec­ond back­end.

What I tell de­ci­sion-mak­ers

Build the lay­er as thin as pos­si­ble. It prox­ies, it ag­gre­gates, it trans­forms, it caches, noth­ing more. The mo­ment some­one pro­pos­es putting "a bit of busi­ness log­ic" in be­cause it's con­ve­nient right now, stop. Busi­ness log­ic be­longs in the sys­tem of record or in a ded­i­cat­ed ser­vice, not in the in­te­gra­tion lay­er. That dis­ci­pline is the dif­fer­ence be­tween an el­e­gant de­cou­pling and a dis­trib­uted mono­lith.

Treat the lay­er as tem­po­rary, even if it is­n't. The fig is meant to re­place the old tree, not clasp it for­ev­er. Mi­crosoft warns, right­ly, that the rout­ing fa­cade must not os­si­fy into en­trenched tech­ni­cal debt. Plan from the start for fea­tures to move out of the lega­cy into real new ser­vices and for the lay­er to shrink ac­cord­ing­ly. An in­te­gra­tion lay­er that's just as fat af­ter three years as on day one has missed its point.

And de­cide hon­est­ly whether you have a fron­tend prob­lem or a back­end in­te­gra­tion prob­lem. If the ag­gre­ga­tion has a fron­tend tie, Next.js is a good, fast-ship­ping choice, and know­ing when Next.js is the wrong choice is part of mak­ing that call hon­est­ly. If it's pure back­end-to-back­end in­te­gra­tion, take a plat­form built for it. The whole mech­a­nism that makes Next.js self-hostable and sov­er­eign to run I've pulled to­geth­er in the overview of Next.js as a strate­gic ar­chi­tec­ture de­ci­sion. The tech­nol­o­gy is­n't the hard part of this pat­tern. The dis­ci­pline is.

Frequently asked questions

Does Next.js replace my SAP or ERP system?
No. Next.js is not a full backend replacement, and the official documentation says so itself. It sits as an integration layer in front: Route Handlers proxy, aggregate and transform data from SAP, ERP or CMS and hide the internal systems behind a modern API. The business logic and the systems of record stay in the backend. The layer only decouples the frontend from the reality of the old systems.
What's the difference between a Next.js BFF and an API gateway platform like Kong or MuleSoft?
A Backend for Frontend is tailored to exactly one frontend experience and ideally owned by the frontend team. It aggregates precisely the data that one surface needs. An API gateway or integration platform solves integration centrally, language-agnostically and for many consumers. For pure backend-to-backend integration with no frontend tie, the platform is usually more dependable. The moment it's about a specific UI, the BFF is closer to the problem.
When does the integration layer itself become the problem?
When it accumulates business logic and chains synchronously into several backends. That produces a distributed monolith: tightly coupled, separately deployed services with latency chains and cascading failures. Even as a pure routing facade it can become a single point of failure and a bottleneck. Once strangulation is complete it has to be thinned out or removed, not left to ossify into permanent technical debt.
Does Next.js caching work self-hosted behind legacy systems?
With a single next start instance on a persistent disk, ISR caching works automatically and takes noticeable load off the legacy backends. With multi-instance or CDN setups you need a custom cache handler, for example on Redis, plus tag coordination across nodes. That distributed cache is the genuinely hard part, and one of the reasons you shouldn't underestimate the layer.

Sources

Related articles

Open for select projects

Let's talk about your project

Book a no-oblig­a­tion call, send us an email, or use the form – we'd love to hear from you.

150+
Completed projects
15
Years of experience
8
Senior‑level team members