Migrating a WPF Desktop App to a Modern Web App: A Practical Playbook
A senior developer's phased playbook for migrating a WPF/.NET desktop app to a React/Next.js web app — API extraction, strangler-fig migration, pitfalls, and how to de-risk the whole move.
I have been on both sides of this wall. I spent several years building WPF applications with MVVM, RelayCommands, and enough custom ValueConverters to last a lifetime. Then I migrated one of those apps to an ASP.NET Web API backend and, eventually, a React front-end. That migration is never just a technical exercise — it surfaces every architectural shortcut the original team made. Here is the playbook I wish I had at the start.
Why Companies Are Finally Making the Move
WPF is not going away, but the cost of staying gets higher every year. The pressures are practical, not philosophical.
- Deployment and patching. Pushing a new version of a WPF app to 200 machines — even with ClickOnce — is friction that web deployment simply does not have. Ship once, everyone is on the new version instantly.
- Hiring. The pool of developers who want to work in WPF is shrinking fast. The pool who know React and TypeScript is the largest it has ever been.
- Device reach. A browser-based app works on a MacBook, a thin client, and a tablet without any installer. That matters when companies standardize on cloud desktops.
- Integration surface. Modern SaaS tools expect webhooks and REST APIs. A WPF app with direct database connections cannot participate in that ecosystem without significant surgery.
Step 1: Assess the App Honestly
Before writing a single line of new code, spend two or three weeks doing a structured audit. The goal is to categorize every screen and subsystem into one of three buckets.
- Pure business logic. Calculations, validation rules, workflow state machines. This translates directly and is your biggest asset.
- UI concerns. Data binding, styling, navigation. This gets replaced entirely — accept that early.
- Platform-specific capabilities. Local file system access, COM interop, serial port communication, direct printing to hardware. These are your risk items. List them explicitly because they need separate solutions or a conscious decision to drop the feature.
Map every direct database call too. WPF apps built before the Web API era often have SqlConnection objects scattered across ViewModels. Document every table and stored procedure being hit. You are going to put an API in front of all of it, so you need to know exactly what "all of it" means.
Step 2: Extract Business Logic Behind an API First
This is the most important phase and the one teams most want to skip. Do not skip it.
Before you touch any UI, introduce an ASP.NET Web API layer that owns all data access. Your WPF app becomes a client of that API. The existing C# domain models, validation logic, and service classes move into the API project. ViewModels stay in the WPF project for now, but they call HTTP endpoints instead of repositories directly.
This phase has two payoffs. First, you smoke-test your entire domain model under a real network boundary — you find serialization edge cases, async patterns you had not considered, and permission logic that was silently handled by Windows Authentication. Second, when you build the React front-end, the API already exists and is already proven. You are not building the backend and the frontend simultaneously, which is the main reason migrations go over budget.
A rule I follow: the Web API must be capable of running the WPF app in production before a single React screen goes to users. If the API is not battle-tested by real traffic first, you are taking two risks at once.
Step 3: The Strangler-Fig Migration
The strangler-fig pattern means you grow the new system around the old one, screen by screen, until the old system has nothing left to do and you turn it off. You never rewrite everything at once.
Pick your least complex workflow as the first target — ideally something with no printing, no file system dependency, and a small number of screens. Build it in React/Next.js, deploy it, put it in front of real users. Learn from that. Then pick the next workflow.
During this period both systems are live. Some users are on WPF screens, some are on web screens. That is intentional. The shared API layer means the data stays consistent. You are essentially doing a rolling migration with a rollback path at every stage.
Step 4: Choosing the Web Stack
For a .NET desktop migration, React with Next.js and TypeScript is the stack I reach for. Here is the reasoning, not the hype.
- TypeScript. Coming from C#, you will not give up static typing. TypeScript covers roughly 80% of what the C# type system gives you for UI code, and your team will feel at home faster than with plain JavaScript.
- React component model. If you understood MVVM, you already understand the separation of concerns React enforces. A component is not that different from a View + ViewModel collapsed into one unit. The data flow direction is just more explicit.
- Next.js. Server-side rendering matters for forms-heavy internal apps because it eliminates whole categories of loading state complexity. The App Router model also gives you a file-system routing convention that is easy to communicate to a team used to WPF navigation patterns.
- React Query or SWR for server state. Your ViewModels were doing manual IsBusy flags and ObservableCollections. React Query replaces that pattern cleanly — loading states, error states, and cache invalidation are handled for you.
The MVVM-to-Component Mental Model Shift
WPF developers sometimes struggle with React because they look for a one-to-one mapping that does not exist. The closest mental model is this: your ViewModel becomes a custom hook. Your View becomes a functional component. Your Commands become event handler functions. Your ObservableCollections become state managed by React Query or useState.
The biggest shift is accepting that the component owns both its markup and its local behavior. In strict MVVM that felt wrong. In React it is the design. Once you stop fighting it, productivity picks up quickly.
Data Layer and Authentication
Most WPF LOB apps use SQL Server with Windows Authentication. Your Web API can keep SQL Server and Entity Framework Core — you do not need to migrate the database. What you do need is an explicit auth system for browser clients. Windows Authentication does not travel over the internet.
For internal apps, integrating with your organization's existing identity provider via OpenID Connect is the standard path. Microsoft Entra ID (formerly Azure AD) is the most common choice in .NET shops and has first-class support in both ASP.NET and Next.js via NextAuth.js. For customer-facing apps, a dedicated auth service like Auth0 or Clerk removes most of the complexity.
Pitfalls You Will Hit
Offline support
WPF apps running on a local network often work when the server is down because data is cached in memory. Web apps are more honest about connectivity. If your users need offline capability, that is a significant separate feature — service workers and IndexedDB — not something you get for free. Decide early whether to build it or change the operational expectation.
Printing and hardware peripherals
Direct printing via WPF's PrintDialog, label printers over USB, barcode scanners, signature pads — all of this needs re-evaluation. Browser printing works for standard documents. Hardware peripherals generally require either a small local bridge service or a WebUSB/Web Serial approach that is supported only in Chromium-based browsers. Do not assume these work until you have tested them on your actual hardware.
Real-time updates
WPF apps in a multi-user LAN environment sometimes do live updates via database polling or WCF push. REST is request-response by default. If your app needs real-time collaboration or live status updates, plan for SignalR (WebSockets via ASP.NET) or Server-Sent Events early. It is not hard, but it is not free either.
De-risking the Migration
The biggest risk in a desktop-to-web migration is not technical — it is organizational. Users trust the old app because it works. Here is what actually reduces the risk of a failed migration.
- Ship the API layer to production before building any web UI. Real traffic finds problems that code review does not.
- Keep the WPF app running in production throughout. Never give users a deadline to switch until the web version has been their default for at least a month.
- Run both systems off the same API. Never maintain two data sources in parallel — that is the path to corrupted state and lost trust.
- Define done for each workflow before you start it. "Done" means users have been using the web version as their primary tool for two weeks, not that the screens render correctly in staging.
What the Migration Actually Buys You
When the migration is complete, you have a system that deploys in seconds, works on any device, integrates with modern tooling, and can attract the engineering talent your business needs to grow. The WPF app got you here. The web app is what gets you to the next decade.
If you are evaluating a WPF-to-web migration for your product and want to talk through the specific risks in your codebase, get in touch — I have been through this migration and I am happy to help you think it through.
FAQ
How long does a WPF to web migration typically take?
It depends heavily on application size and data complexity, but a mid-sized line-of-business WPF app typically takes 6-18 months using a strangler-fig approach. The API extraction phase alone can run 2-3 months, but it is the work that makes the rest safe.
Can we keep the existing SQL Server database when migrating from WPF to web?
Yes. Introducing an ASP.NET Web API layer in front of the existing database is the standard first step. You are not touching the schema at that point — just moving data access behind an HTTP boundary. Schema cleanup is a later, optional concern.
What is the best web framework for replacing a WPF app?
React with Next.js and TypeScript is the most practical choice for most .NET desktop migrations. The component model maps well to MVVM mental models, the TypeScript type system replaces the safety net that C# gave you, and Next.js handles routing, SSR, and deployment in a way that WPF developers find familiar in structure.
Do we have to rewrite the entire app at once?
No, and you should not. The strangler-fig pattern lets you ship the new web screens one workflow at a time while the old WPF app stays in production. Users transition gradually and you can stop or roll back at any stage.