Desktop-to-Web Application Migration
A desktop-to-web migration done well is not a from-scratch rebuild of the application in a browser — it is the incremental extraction of business logic out of the thick client into a service tier, with a web front end built on that tier one workflow at a time. You don't rewrite the whole product; you carve out a service boundary the desktop app never had, move logic behind it slice by slice, prove each extraction behaves identically to the desktop original, and build the web UI on the growing API workflow by workflow. The desktop product keeps running and shipping the entire time, customers adopt the web experience gradually rather than through a forced cutover, and the two front ends can coexist on the same logic during the transition. That is what turns the daunting "rewrite it for the web" into a sequence of ordinary, low-risk releases instead of a multi-year big-bang bet.
Part 3 laid out the slice-by-slice method in full for a product that ships to customers. This part applies it to the transition ISVs request more than any other: moving a thick desktop client to the web. It is the migration most likely to be framed as a rewrite — “let’s rebuild it for the browser” — and therefore the one most likely to fail. Done as a sequence of slices instead, it becomes a series of ordinary releases with the desktop product running the whole time.
The whole choice comes down to two paths, and they diverge sharply on risk.
| From-scratch web rewrite | Incremental extraction | |
|---|---|---|
| The desktop product during the work | Frozen, then replaced | Keeps running and shipping |
| First customer value | At a distant finish line | Workflow by workflow, early |
| Undocumented behavior | Rediscovered, gaps ship as regressions | Recovered and parity-proven per slice |
| Cutover | One risky flag day | None. Customers adopt gradually |
| If it goes wrong | The whole product, at once | One workflow, reversible |
| Fits when | A trivial app with no accumulated behavior | A real product with years of embedded rules |
The rest of this part is how the right-hand column actually works.
Split the problem in two
The reason desktop-to-web migrations turn into doomed rewrites is that teams treat the application as one indivisible thing to be reproduced in a browser. It is not. There are two problems hiding inside it, and they have to be separated.
The first is the business logic — the rules, calculations, workflows, and accumulated special cases that make the product valuable. In a client-server desktop app this logic is trapped inside the thick client and the database, tightly bound to the desktop UI. The second is the user interface — the windows, forms, and interactions the user touches. The instinct is to rebuild both at once for the web. The discipline is to recognize that only the second one is genuinely web-specific. The logic is not desktop logic or web logic; it is product logic that happens to be imprisoned in a desktop shell. Free it, and the web migration becomes mostly a new front end on logic that already works.
Step one: extract the logic into a service tier
So the real first move in a desktop-to-web migration is the architectural one from Part 2: grow the service tier the desktop app never had. You extract business logic out of the thick client and the database into services behind a stable API, one slice at a time. A workflow at a time, a domain at a time, the logic stops living inside the desktop UI and starts living behind an interface that any client — desktop, web, mobile, an integration — can call.
This is the slice-by-slice loop applied with one front end already in place. The desktop client is rewired to call the new service for the extracted slice instead of doing the work itself, and — this is the key — the desktop product keeps working exactly as before, because the service produces the same results the inline logic used to. That equivalence is not assumed; it is proven. Parity validation confirms each extracted slice behaves identically to the desktop original, across the environments customers run, before it ships in an ordinary release. The desktop app gets quietly better-architected with each release while looking unchanged to the user.
Step two: build the web UI on the growing API, workflow by workflow
Once logic lives behind the service tier, the web front end has something to be built on. And it gets built the same incremental way: workflow by workflow, not all at once. You bring the highest-value or most-requested workflow to the web first — built on the services already extracted for it — ship it, and let customers use it. Then the next workflow, and the next. The web application grows in coverage release by release, each increment delivering real value the moment it ships rather than waiting for a complete browser replica of the desktop product.
The pattern underneath is again the strangler fig: the web front end strangles the desktop one a workflow at a time, taking over more of the product with each release until the desktop client can eventually be retired — if and when the business decides to. There is never a flag-day cutover from desktop to web. There is a steadily growing web product and a steadily shrinking reliance on the desktop one. This is the same incremental-frontend discipline the SaaS series describes for modernizing a web UI, applied to crossing from desktop to web in the first place.
Why coexistence is the whole point
The property that makes this safe is that the desktop and web front ends run on the same service tier during the transition. Because the logic lives behind shared services rather than inside either client, the two front ends cannot drift apart — they compute the same results because they call the same code. You are not maintaining two copies of the business rules and praying they stay in sync; you are maintaining one core with two faces.
That coexistence is what lets you move the customer base on its own schedule. Some customers adopt the web version immediately; some keep using the desktop client for workflows it still does better; some run both, different users on different front ends, against the same data and logic. For an installed-software vendor whose customers upgrade when they choose, this is essential — there is no forced migration, no moment where a customer is told the desktop product is gone before they are ready. The web experience simply becomes available and grows more capable, and customers migrate as it earns their workflows.
The behavior you must not lose
The single biggest risk in this migration is the same one that dooms the rewrite: losing the undocumented behavior the desktop product accumulated over years. The special case added for one large customer, the validation rule everyone forgot was there, the exact way a calculation rounds — this tribal knowledge is buried in the thick client, and the field depends on it whether or not anyone remembers it. A from-scratch web rewrite has to rediscover all of it and ships the gaps as regressions; the incremental extraction recovers it slice by slice and proves each slice matches before it ships. The web product inherits the desktop product’s hard-won correctness instead of starting blank — which is most of why the incremental path succeeds where the rewrite fails.
What does not translate, and what to do about it
The method above is about the logic and the workflows. But a desktop application also does things a browser was not originally built to do, and honesty about that gap is what separates a plan that lands from a demo that impresses and then stalls. A thick client can reach the file system freely, talk directly to local hardware, run fully offline for hours, integrate tightly with other installed software, and stay instantly responsive on very large data sets held in local memory. Some of that carries over to the web easily. Some of it does not.
The move is not to pretend the gap away and not to let it veto the migration. It is to map each native capability the product actually uses, decide per capability whether the web can match it, work around it, or drop it, and surface that decision before the migration commits rather than discovering it at customer rollout.
| Native capability | Web reality | The honest move |
|---|---|---|
| Reading and writing local files | Browser file APIs handle picked files well, silent full-disk access no | Redesign the flow around explicit file access, or use a desktop shell where deep access is essential |
| Local hardware (scanners, scales, instruments) | Some devices reachable through browser device APIs, many not | Confirm each device early. A small local agent or packaged shell bridges what the browser cannot reach |
| Fully offline for long stretches | Caching and sync cover intermittent gaps, true long offline is costly | Scope the real offline need up front and budget for it, or confirm it can be relaxed |
| Deep integration with other installed apps | Limited from a browser sandbox | Replace with server-side or API integrations where possible, shell where not |
| Instant response on very large local data sets | Achievable but needs deliberate work, not free as it was in memory | Move heavy computation server-side, page and virtualize the UI |
The reassuring part is that the service-tier architecture already built for the migration is what makes these choices cheap to act on. Because the logic lives behind a shared API, delivering the web front end as a plain browser app or wrapping the same front end in a desktop shell for the cases that need native reach is a front-end decision, not a second rewrite. You are choosing how to deliver one core, not building two products.
When the slow start doesn’t pay off
Extracting logic before building the web UI is slower at the start than opening a blank browser project and coding away. The early releases are unglamorous — services moving behind an API while the visible product looks unchanged — and it takes discipline to fund that groundwork before the web UI appears. The payoff is that the web product, when it arrives, sits on proven, shared logic and the desktop product never broke. For a product with one trivial workflow and no accumulated behavior, a direct rebuild might genuinely be simpler. For a real product with years of embedded rules and a customer base that depends on them, the incremental path is what keeps the migration from becoming the rewrite that fails.
Where this leads
This part has crossed from desktop to web without a rewrite, by extracting logic into a service tier and building the web UI on it workflow by workflow, with both front ends coexisting on shared, parity-proven services. A web front end usually raises the next architectural question immediately: should the backend still run as a separate install per customer, or serve many customers from one deployment? Part 5, Multi-Tenant Migration for ISVs, takes on the shift from single-tenant installs to a multi-tenant backend — the change that makes modern, cloud-style delivery possible.
Frequently asked questions
- How do you migrate a desktop application to the web without rewriting it?
- You separate the migration into two problems: the business logic and the user interface. The logic is extracted from the thick client into a service tier behind a stable API, one slice at a time, with each extraction proven to behave identically to the desktop original before it ships. The web front end is then built on that API workflow by workflow. Because the desktop client can call the same extracted services, both front ends run on one shared core during the transition — so you are migrating incrementally, not rebuilding the whole application in a browser at once.
- Can desktop and web versions of a product run at the same time?
- Yes, and that coexistence is what makes the migration safe. Once business logic lives in a shared service tier rather than inside the desktop client, both the desktop app and the new web app can call the same services, so they stay behaviorally consistent without maintaining two copies of the logic. Customers can keep using the desktop product while the web version covers more workflows over time, and you can move the customer base gradually rather than forcing a single cutover from one to the other.
- What is the biggest mistake teams make migrating desktop apps to the web?
- Treating it as a from-scratch rewrite of the entire application for the browser. That approach freezes the desktop product, tries to reproduce years of accumulated, undocumented behavior all at once, and delivers nothing until a distant finish line — which is why so many such projects fail. The smaller, safer path is to extract the logic into a service tier incrementally and build the web UI on it workflow by workflow, keeping the desktop product alive and shipping while the web one grows up beside it on shared, parity-proven services.
- What desktop features are hardest to replicate on the web?
- The ones that depend on the machine rather than the application: deep file-system access, direct communication with local hardware like scanners, scales, or lab instruments, tight integration with other installed software, high-end offline operation, and the raw responsiveness of a native UI on very large data sets. Modern browsers close much of this gap through their device and file APIs, but some of it does not translate cleanly and has to be re-solved or deliberately dropped. Naming these constraints early, before the migration commits, is what keeps them from becoming late surprises.
- How do you handle offline use when moving a desktop app to the web?
- Decide first how much offline capability the product genuinely needs, because it is one of the larger costs in the move. A browser application can cache assets and data and sync when a connection returns, which covers intermittent connectivity well. Fully offline operation for long stretches is harder and more expensive to build than it was on the desktop, where it came for free. If real customers work disconnected for hours, that requirement should shape the plan from the start rather than being bolted on after the web UI exists.
- Should the web version be a browser app or a packaged desktop-web hybrid?
- It depends on how much native capability the product must keep. A plain browser application is the simplest to deliver and update and is the right default when the workflows fit what a browser can do. When the product needs deep local integration that browsers still cannot reach, packaging the same web front end inside a desktop shell keeps one codebase while restoring native access. The service-tier architecture makes this a front-end delivery choice rather than a rebuild, because both options run on the same extracted services.