
A frontend transformation of a fulfilment platform, migrated module by module with nothing paused

Project goal
To clear years of frontend technical debt from a platform that warehouses run on every day, without freezing the product or interrupting a single shift.
Our scope
Architecture analysis
Migration strategy
UX/UI design
Frontend development
Mentoring & AI training
Project duration
Since 2021
Technologies used
React
React Router v7
TypeScript
Vite
SWR
Client
Omnipack
Five years in, Omnipack handed us the platform the business runs on
We started working with Omnipack in 2021, on their website. That went well enough that when the company decided to tackle something far heavier, its own fulfilment platform, they came to us for that too. Omnipack is one of the largest fulfilment providers in Poland, so the platform is not a side product. It is the thing merchants log into to run their logistics, the thing warehouse staff work in all day. Being trusted with it is the part of this project we are proudest of.

Two systems, one login apart, with very different problems
The platform has two halves. The Client Portal is what Omnipack's merchants use to manage products, stock, deliveries, orders, returns and settlements. The Backoffice is what the operations and warehouse teams use. It runs to somewhere between twenty and thirty modules covering returns handling, dispatch, packing rules, inventory counts, customer communication and the rest. Both had been built up over years on a Java monolith with Thymeleaf and React mixed together. Both needed the same thing. Neither could be treated the same way.

What years of uninterrupted growth had done to the frontend
The platform had been developed continuously for years, with the backend modernised steadily along the way. The frontend grew alongside everything else without a shared architecture or a common set of standards to hold it together, which is how a codebase ends up where this one had: several versions of React running at once, several libraries solving the same problem, several ways of styling a component, no design system, duplicated components and business logic, React tangled into Thymeleaf. Every one of those is survivable on its own. Together they turn shipping a feature into an argument with the codebase.

Some of the system existed only in the code
There was a second problem underneath the first. Documentation was incomplete, so a fair amount of what the platform did had never been written down anywhere. It lived in the code, plus in the habits of the people using it. That meant a rewrite could not start from a specification, because there was not one.

So every module got taken apart before anything was rebuilt
Each module went through the same treatment. We read the old frontend and the backend behind it, mapped every endpoint a given view actually called, traced the data flows and hunted for the behaviour nobody had documented. Then we talked to the people who use it, because a warehouse operator knows things about a returns screen that no repository will tell you. Out of that came the mockups, the UX improvements worth making and a list of the endpoints that would need to exist. Those went to Omnipack's backend team, where the API changes were designed together.

Two halves, two migration strategies
The Client Portal was rebuilt as one product. Migrating it module by module would have left merchants moving between two different interfaces inside one application, which is a bad experience to hand someone, so the whole new portal was developed in parallel and put behind feature flags. That let the team test safely in production, flip between old and new in a moment, move users across gradually and keep the risk low the whole way. The Backoffice went the opposite way, module by module, because its twenty-odd modules serve different teams and can stand alone. That let us deliver value in the first weeks rather than the first year, take the most painful modules first and retire pieces of the old system as we went. The first new modules were live in production within a month of starting.

The product never stopped shipping
Migrations like this usually arrive with a bargain attached: put the roadmap on hold while we rebuild. We took that off the table at the start. New modules went to production continuously, while new business features were built alongside them, so Omnipack kept getting value the entire time rather than waiting for a migration to finish. A rewrite that pauses the roadmap is a rewrite the business quietly starts to resent. This one never gave anyone the chance.
A new frontend architecture the platform can grow into
The new frontend is a React Router v7 single-page app built on Vite and TypeScript. It is organised feature by feature, so each module lives as its own self-contained slice instead of spilling across the codebase. Everything reaches the backend purely through the API, which draws a clean line between the two sides. That line is what will let the old frontend be switched off entirely once the migration is finished.

A design system, so the platform finally looks like one product
Alongside the architecture we built the design system the platform had never had: colours, typography, design tokens, forms, tables, dialogs, reusable components and the patterns that tie them together. We implemented it on shadcn/ui and Radix, which meant we moved quickly while keeping Omnipack's own visual identity and kept the freedom to extend a component wherever a screen needed something particular. Twenty modules built by different people over several years now look like they belong to the same system, because they do.

Guardrails so the debt does not quietly come back
Clearing technical debt is easy to undo, so the new codebase defends itself. Linting and formatting are enforced by Biome, types are checked on every commit, unused code is detected automatically and pre-commit hooks run all of it in parallel before anything lands. Conventions are documented in the repository rather than held in someone's head. None of this is glamorous. It is the difference between a clean codebase and a codebase that was clean once.
AI in the daily work, then taught to the client's team
A codebase this size with documentation this thin is exactly where AI earns its keep, so we used Claude throughout: reading unfamiliar code, mapping endpoints, identifying what a module actually did, generating and refactoring code, writing the documentation that had been missing. Then we taught it. We ran mentoring for Omnipack's developers on using these tools in real daily work. The productivity gain landed on both sides, frontend and backend alike.
One useful side effect - plugging our own clients into Omnipack got easy
Something came out of this project that nobody planned for. We build e-commerce stores too, several of which run their fulfilment through Omnipack. Working on the platform itself means we know its API from the inside, so wiring a shop into it is no longer a matter of reading documentation and hoping for the best. We already know what the endpoints do, along with where the awkward parts hide. An integration that normally calls for slow, cautious work now goes in quickly, which our clients get the benefit of without ever knowing why.
Outcome
The Client Portal has been fully modernised. The Backoffice migration is running module by module. The architecture is coherent, the design system is in place, a large part of the technical debt is gone, onboarding a new developer takes a fraction of what it did and features ship faster than they used to. Delivery sped up to the point where the constraint stopped being development at all and became the client's own QA process.
Let's talk about your project idea!
Tell us about your project. We’ll help you plan the architecture, scope, and execution.
© Webalize 2026


