Skip to content
← Case studies

Payment widget — one Next.js widget for every app and the start of migration

My first Doccle feature was payments, needed inside the old app and on a separate quick-payment page. Rather than extend the old code, I built a separate Next.js app, so an urgent feature started the migration.

Instead of building the widget twice, I built it once: the payment page and the old app, which shows it in an iframe, share one codebase and one new API. I compared three options and convinced a team that wanted to stay on the old stack, taking the risk myself.

It became a core part of the business, and new payment products that people actively use grew on it.

Context

Doccle is a digital document platform used in Belgium by 2.5 million people and integrated with more than 150 companies. Its apps ran on Grails and Java, with parts in Angular, and the company wanted to move them to Next.js, React and TypeScript.

In 2022 I joined as the only frontend developer on the team, hired to lead that migration. I worked with backend developers, the product owner, designers and QA, and I worked on the payments from 2022 to 2026.

Problem

The first feature the business gave me was payments, and it needed them in two places. Inside the old app, people needed to pay their documents through a new payment widget; outside it, anyone with a link had to pay on a separate quick-payment page called EasyPay. The business wanted a first version within a few months.

The obvious way was to build it where payments already lived. But the old app was a mix of Grails and Java with some Angular and a little React, and every feature added there made the migration bigger and pushed it further away.

Building the two places separately had its own cost. The same widget would exist twice, with the same logic and components written and fixed in two codebases, and every later change to payments would have to be made twice too.

Objectives

For the business, the goal was a working payment widget in both places on its timeline. For the product, we wanted one codebase to maintain instead of two, and a first real step onto the new stack instead of more code on the old one.

We would know it worked when people paid through the new widget in the old app and on the quick-payment page, and when new payment features could be added in one place.

Research

Before choosing how to build it, I had to know what the old payment flow did. It had been built over the years by many developers, some had left, and nobody knew all of its behaviour and edge cases for sure.

So I pieced it together myself: I went through the old code, asked the product managers and talked to the developers who still remembered parts of it. From that picture I agreed with the product owner which part had to come first.

I also had to judge whether a separate app shown inside the old one was realistic. I had built web applications before and knew how iframes work, so I knew it was realistic.

Options

I wrote the choice up in an architecture decision record with three options, each with its cost:

  • Everything on the old stack. Both the widget in the old app and the EasyPay page in Grails, Java and Angular. The fastest and the safest, but half a year of new legacy code, and the migration would keep moving away.
  • EasyPay on Next.js, the widget on the old stack. A new page, but the same logic and all its components written twice, on two different stacks, and maintained twice from then on.
  • A separate Next.js app, shown inside the old one through an iframe. One codebase for both places, on the new stack and the new API. It moves only the payments, so it is the cheapest way to start the migration. The catch: the team had never worked this way, and it could take longer.

The first two were safe in the short run but cost us either the migration or double the work. Only the third avoided both.

Design

I chose the separate Next.js app, because it gave three things at once. With one codebase, every fix and every new feature was made once, which made support and development much easier. It started the migration on the new stack instead of adding to the old one. And both places used the same new API.

Convincing the team

The team wanted to stay on Grails. They had never worked with this kind of architecture and didn't believe I could build a new app with all its components quickly enough, or that the iframe would hold up. I showed them the decision record, explained from my experience why it was realistic, and took the risk of the decision on myself.

One codebase, two places

I created the Next.js app with its structure and components and built a working prototype of the EasyPay page first, in about 2 months; the business approved it and we built on it. About three weeks after it was ready, the same base was serving pages that the old app shows inside an iframe, and two months later it took payments on public document pages too. The first release went out within 6 months. The pages differ, but they share the components and the logic, and I covered them with tests and mocks from the first month.

A temporary layer that lasted three years

We knew from the start that the iframe was temporary, only until the old app was migrated, but at that point it was the best choice. It held for three years. All that time we kept adding new features to the widget and kept them working in the old app through the same iframe. That showed how flexible the architecture was.

Ready for new payment products

In 2024 I took the next step. I moved the payment app into the monorepo where the rest of the code already lived, so that it used the shared design system instead of its own components, and I adapted the widget so that it is easy to customise and to add new steps to.

Results

The widget became a core part of the business, and people pay through it in four places: the EasyPay page, the old app, public document pages and, since 2026, the website widget.

On the step-based widget, the business kept launching new payment products that people actively use: payment plans, new payment methods, recurring payments, scheduling and cancelling payments, and saved cards for faster checkout. I built the recurring payments, cancelling and saved cards.

Next.js Crash CourseStarting with Next.js? My course covers routing, layouts, SSR and data fetching in a weekend

Looking back

It was worth it. Had we built on the old stack, every payment feature since would have landed in legacy code; had we built it twice, we would still be maintaining two widgets.

The migration went the way we planned, too. We left the payment page for the very end, and by the time it was the only page left, the components and the architecture were already in place. That made it much clearer how to move it, and in the end we migrated this last page into the new app too and dropped the iframe.

What I'm looking for

A senior full-stack, senior frontend or frontend lead role in a product company or startup. I'm based in Wrocław, Poland (CET) and work remotely.

What people say

Thomas AnciauxLead Front-End Engineer at Doccle · LinkedIn

Dmytro has solid knowledge and extensive experience with TypeScript, React and Next.js, and a good grasp of building maintainable, well-structured interfaces.

He delivers clean code consistently, with a lot of attention to the details. Dmytro took ownership of his work, thought through edge cases, and could be counted on to follow through on what he shipped.

Wesley MostienLead Front-End + Node.js Developer, managed Dmytro directly · LinkedIn

I worked with Dmytro on the same project, where we collaborated closely on the frontend side. Dmytro is a highly skilled frontend developer with strong experience and a solid technical mindset.

He consistently delivered features with good quality, paid attention to clean architecture, and cared about maintainable code. He was also easy to collaborate with, communicated clearly, and contributed positively to the team.

Dmytro is just great to work with. Very knowledgeable and keeps working at his craft.

Alexander ShelestovEx CTO · LinkedIn

It was a hard time when we had to choose and implement new tools: JS framework, CSS classes naming conventions, project structure, testing, etc.

Dmytro started as an ordinary frontend developer and just after a few weeks led the whole process as a leader. Dmytro found a way to compare the different approaches and choose the right one for our project.

Etienne AdriaenssenEntrepreneur, Dmytro's client · LinkedIn

I worked with Dmytro on a healthcare startup called DasLab. He joined very early on and was a great asset in our growth. He showed a real expertise in Vue.js, large codebase management and engineering processes.

On the management side too he was fantastic, he helped me build a team in Ukraine and together we delivered over 1m Covid Tests.

Phil O'MalleyChief Technology Officer · LinkedIn

Dmytro is a strong UI JS Developer who has made valuable contributions to his Scrum since joining the Travelport Locomote Development group. He has been a strong proponent within the group for UI Framework improvements and refactoring and has worked closely with his team and with the UI Architect on this effort.