{"id":161,"date":"2026-06-06T21:46:58","date_gmt":"2026-06-06T21:46:58","guid":{"rendered":"https:\/\/softwareproduction.eu\/wordpress\/?p=161"},"modified":"2026-06-07T01:21:27","modified_gmt":"2026-06-07T01:21:27","slug":"the-legacy-problem-when-manual-synchronization-becomes-the-architecture","status":"publish","type":"post","link":"https:\/\/softwareproduction.eu\/?p=161","title":{"rendered":"The Legacy Problem \u2014 When Manual Synchronization Becomes the Architecture"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><em>First in a series about migrating from legacy web architectures to a modern Nuxt 4 stack. The series covers architecture, code generation, performance, infrastructure, and the automation philosophy behind every decision.<\/em><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The Starting Point: Architectures That Work \u2014 But Barely<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Across multiple large, high-traffic, customer-facing web platforms \u2014 the kind where millions of visitors per year browse products, configure options, and complete purchases \u2014 two architectural patterns show up again and again:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Dual-rendering<\/strong>: ASP.NET MVC with Razor views and Vue.js components layered on top. The server renders HTML; the client re-renders parts of it.<\/li>\n<li><strong>SPA + BFF<\/strong>: A single-page application handles all rendering, calling a backend-for-frontend (BFF) layer for data. Types, REST clients, and mapping code are written by hand on both sides.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Both patterns work. Pages load, forms submit, orders go through. But both are collapsing under complexity that better code cannot fix. The problems are structural \u2014 rooted in <strong>manual synchronization between client and server code<\/strong> that compounds with every new feature.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This article walks through the patterns repeatedly seen in these systems, why they decay, and what finally pushes teams to break out.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The Architecture That Grew Sideways<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Variant A: Dual Rendering (Server HTML + Client JS)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The most visible version of this problem follows a pattern that sounds sensible at first: <strong>server-side HTML rendering through Razor templates, with client-side interactivity layered in via Vue.js components<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The server renders page structure and static content. Vue components handle the dynamic pieces \u2014 a product configurator, a checkout form, an interactive filter. The two worlds are stitched together with data attributes, JSON blobs injected into the Razor HTML, and an ever-growing pile of initialization scripts.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here is a simplified view of how a single page request flows through such a system:<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart TB\n    subgraph B[\"Browser\"]\n        Breq[\"User requests page\"]\n    end\n\n    subgraph S[\"ASP.NET MVC Server\"]\n        C[\"Controller\"]\n        VM[\"C# ViewModel\"]\n        RZ[\"Razor View\"]\n        HTML[\"Server-rendered HTML&lt;br\/&gt;+ embedded JSON blobs&lt;br\/&gt;+ &amp;lt;script&amp;gt; tags mounting Vue\"]\n    end\n\n    subgraph CS[\"Client-Side (Browser)\"]\n        PHTML[\"Step 1: Parse large HTML document\"]\n        PJS[\"Step 2: Download all JS bundles (no splitting)\"]\n        PJSON[\"Step 3: Parse JSON blobs from data attributes\"]\n        MOUNT[\"Step 4: Mount Vue components onto DOM nodes\"]\n        RERENDER[\"Step 5: Re-render with client-side data\"]\n        CLS[\"\u2192 Content jumps (CLS)\"]\n        TBT[\"\u2192 Slow interaction (TBT)\"]\n    end\n\n    Breq --&gt; C --&gt; VM --&gt; RZ --&gt; HTML\n    HTML --&gt; PHTML --&gt; PJS --&gt; PJSON --&gt; MOUNT --&gt; RERENDER\n    RERENDER --&gt; CLS\n    RERENDER --&gt; TBT<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This is a <strong>dual-rendering architecture<\/strong>: the server renders HTML, and the client re-renders parts of it. Neither side has the full picture. The gap between what the server knows and what the client knows is where the bugs live.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Variant B: SPA + BFF (Client Renders Everything, Server Provides Data)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The second variant looks more modern on the surface. A single-page application \u2014 React, Angular, or Vue \u2014 handles all rendering. The server exposes REST APIs (often through a BFF layer), and the client fetches data via HTTP.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">There is no dual rendering, no hydration gap, no content flash. But the <strong>manual synchronization problem is identical<\/strong>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Every API endpoint needs a corresponding TypeScript interface on the client<\/li>\n<li>Every DTO in C# or Java needs a hand-written model in TypeScript<\/li>\n<li>Every REST client is bespoke: custom fetch wrappers, error handling, retry logic, response parsing<\/li>\n<li>Every new field or renamed property requires coordinated changes across both codebases<\/li>\n<li>Every API version bump triggers a manual update cascade through the frontend<\/li>\n<\/ul>\n\n\n\n<pre class=\"mermaid\">flowchart LR\n    subgraph SPA[\"SPA (React\/Angular\/Vue)\"]\n        UI[\"Components\"]\n        TSM[\"Hand-written TS models\"]\n        RC[\"Hand-written REST clients\"]\n    end\n\n    subgraph BFF[\"BFF \/ Backend\"]\n        CTRL[\"Controllers\"]\n        DTO[\"DTOs \/ ViewModels\"]\n        SVC[\"Services\"]\n    end\n\n    UI --&gt; TSM --&gt; RC --&gt; CTRL --&gt; DTO --&gt; SVC\n\n    classDef warn fill:#fff2e6,stroke:#e67e22,stroke-width:1px,color:#000;\n    TSM:::warn\n    RC:::warn\n    DTO:::warn<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The rendering is clean, but the <strong>data contract layer<\/strong> \u2014 the types, the clients, the mapping \u2014 is just as manual and just as fragile as in the dual-rendering case. A renamed field in the backend does not produce a compile error in the frontend. It produces a <code>undefined<\/code> at runtime, caught by QA if you are lucky, or by a customer if you are not.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Teams in this position often generate TypeScript clients from OpenAPI specs, which helps. But it only covers the REST surface. If the frontend also consumes a headless CMS, a SOAP service, or multiple microservices with their own contracts, those sources still need manual type definitions. The problem is reduced but not eliminated \u2014 and the REST clients themselves still carry per-endpoint boilerplate: headers, auth tokens, error mapping, pagination handling, cache invalidation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Both variants converge on the same core issue: <strong>manual synchronization of data contracts across a network boundary, at a scale where humans cannot keep up<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Five Problems That Compound<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The following problems manifest in both architectural variants, though the first (the hydration gap) is specific to dual-rendering systems. The remaining four apply equally to SPA + BFF architectures.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">1. The Hydration Gap<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Modern SSR frameworks like Nuxt or Next.js solve the &#8220;server renders, client takes over&#8221; problem with <strong>hydration<\/strong>: the client receives the exact same component tree the server rendered and <em>attaches<\/em> to it without re-rendering. The DOM stays stable. The user sees no flicker.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A dual-rendering architecture has no such mechanism. Vue components mount <em>after<\/em> the server-rendered HTML is already visible. The browser shows the Razor output first \u2014 then, once JavaScript loads and runs, Vue replaces sections of the DOM with its own rendered output. The result is visible content jumps (Cumulative Layout Shift) and a page that feels slow even when the server responds quickly.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In practice, this often looks like a user landing on a product comparison page and seeing a static HTML shell for 1\u20132 seconds, followed by a jarring re-render as Vue components initialize and inject dynamic content. On mobile devices with slower CPUs, the effect is even worse.<\/p>\n\n\n\n<pre class=\"mermaid\">sequenceDiagram\n    participant U as User\n    participant Br as Browser\n    participant S as ASP.NET MVC + Razor\n    participant JS as Vue JS Runtime\n\n    U-&gt;&gt;Br: Navigate to product comparison page\n    Br-&gt;&gt;S: HTTP GET \/products\/compare\n    S--&gt;&gt;Br: HTML (Razor-rendered shell&lt;br\/&gt;+ JSON blobs + script tags)\n    Br--&gt;&gt;U: Initial static HTML rendered&lt;br\/&gt;(*visible to user*)\n\n    par Network &amp; CPU\n        Br-&gt;&gt;JS: Download &amp; parse JS bundles\n        JS-&gt;&gt;Br: Initialize Vue runtime\n        JS-&gt;&gt;Br: Mount Vue components&lt;br\/&gt;and re-render sections\n    end\n\n    Br--&gt;&gt;U: DOM updates cause&lt;br\/&gt;content jumps (CLS) and delay&lt;br\/&gt;before interactive (TBT)<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">2. The Data Translation Layer<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">In these architectures, every piece of data that must appear in both server-rendered HTML and client-side Vue components has to be <strong>manually translated<\/strong> between two type systems:<\/p>\n\n\n\n<pre><code class=\"language-text\">C# ViewModel (Server)          TypeScript Model (Client)\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500          \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nProductOfferModel.cs     \u2192     ProductOffer.ts\n  .PriceGross (decimal)           .priceGross (number)\n  .ProductName (string)           .productName (string)\n  .ValidFrom (DateTime)           .validFrom (string)\n  .IsDefault (bool)               .isDefault (boolean)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">For every backend data model, a developer has to:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Write a C# ViewModel class  <\/li>\n<li>Write a corresponding TypeScript interface  <\/li>\n<li>Write a mapping function that transforms the C# object into JSON  <\/li>\n<li>Write another mapping function that deserializes it on the client side  <\/li>\n<li>Keep all four in sync whenever any field changes  <\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">This translation layer is tedious, but the real problem is that it creates <strong>constant bugs<\/strong>. A renamed field in C# silently breaks the client. A new enum value triggers a runtime error in JavaScript. A date format change causes forms to display the wrong values.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Some teams address this with <strong>OpenAPI<\/strong> (Swagger) by generating TypeScript clients from the backend&#8217;s REST API spec. For pure REST architectures, that is a solid approach. But it only covers the REST surface. If the frontend also consumes a headless CMS, a SOAP service, or server-rendered JSON blobs, those sources still need their own manual type definitions. OpenAPI reduces the problem; it does not eliminate it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You end up with dozens of model pairs, each one a potential point of divergence.<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart LR\n    subgraph Server[\"Server (C#)\"]\n        VM[\"ViewModel class&lt;br\/&gt;(e.g., ProductOfferModel.cs)\"]\n        MAP_S[\"Mapping function:&lt;br\/&gt;C# \u2192 JSON\"]\n    end\n\n    subgraph Wire[\"Wire Format\"]\n        JSON[\"JSON payload\"]\n    end\n\n    subgraph Client[\"Client (TypeScript\/Vue)\"]\n        TSIF[\"TypeScript interface&lt;br\/&gt;(ProductOffer.ts)\"]\n        MAP_C[\"Mapping\/parsing function:&lt;br\/&gt;JSON \u2192 TS model\"]\n        VC[\"Vue component\"]\n    end\n\n    VM --&gt; MAP_S --&gt; JSON --&gt; MAP_C --&gt; TSIF --&gt; VC\n\n    classDef warn fill:#fff2e6,stroke:#e67e22,stroke-width:1px,color:#000;\n    VM:::warn\n    TSIF:::warn\n    MAP_S:::warn\n    MAP_C:::warn<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">3. No Unified API<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">In legacy systems that grew organically, the frontend usually does not talk to a single API. Instead, it calls:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>REST endpoints for business logic and transactional operations  <\/li>\n<li>Direct headless CMS REST APIs for marketing or content pages  <\/li>\n<li>Server-rendered JSON blobs for other server-controlled content  <\/li>\n<li>Various cloud service endpoints for user data, feature flags, or personalization  <\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Each integration has its own error handling, retry logic, response format, and TypeScript types. There is no shared understanding of what &#8220;calling the backend&#8221; even means.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The result: <strong>every new feature requires figuring out which endpoints to call<\/strong>, in what order, and how to stitch the responses together. Frontend developers spend more time reading backend code than writing frontend code.<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart LR\n    FE[\"Frontend app&lt;br\/&gt;(Vue in Razor pages)\"]\n\n    subgraph APIs[\"Backend &amp; Services\"]\n        REST[\"Business REST API\"]\n        CMS[\"Headless CMS REST API\"]\n        JSONB[\"Server-rendered JSON blobs\"]\n        CLOUD[\"Cloud services&lt;br\/&gt;(flags, personalization, auth, etc.)\"]\n    end\n\n    FE --&gt; REST\n    FE --&gt; CMS\n    FE --&gt; JSONB\n    FE --&gt; CLOUD<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">4. Performance as an Afterthought<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Performance in these systems is rarely designed in \u2014 it is patched over with operational over-provisioning. The usual pattern looks like this:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Metric<\/th><th>Value<\/th><\/tr><\/thead><tbody><tr><td><strong>Median response time<\/strong> (homepage)<\/td><td>2,618 ms<\/td><\/tr><tr><td><strong>Error rate<\/strong> under normal load<\/td><td>3.91%<\/td><\/tr><tr><td><strong>JavaScript bundle size<\/strong><\/td><td>No code splitting \u2014 everything loaded upfront<\/td><\/tr><tr><td><strong>Infrastructure<\/strong><\/td><td>3\u00d7 P3v3 App Service instances (24 vCPU, 96 GB RAM)<\/td><\/tr><tr><td><strong>Scaling model<\/strong><\/td><td>Fixed \u2014 always on, always paying<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">A 2.6-second median response time is usually not a server problem \u2014 the C# backend is often reasonably fast. The problem is the <strong>rendering pipeline<\/strong>: large HTML pages, no code splitting, no lazy loading, uncompressed assets, and the sequential load-parse-mount-re-render cycle Vue must perform on every page view.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Throwing hardware at the problem \u2014 and three P3v3 instances is a <em>lot<\/em> of compute \u2014 keeps the site alive, but it does not make it fast. And you pay the same at 3 AM with zero traffic as you do at noon during peak load.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">5. Developer Experience Death by a Thousand Cuts<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Adding a new content section \u2014 say, a promotional banner with a countdown timer \u2014 can require touching <strong>six distinct layers<\/strong>:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>C# TagHelper<\/strong> \u2014 to define how the server renders the banner&#8217;s outer HTML  <\/li>\n<li><strong>C# ViewComponent<\/strong> \u2014 to load data for the banner from a service  <\/li>\n<li><strong>CSHTML Razor View<\/strong> \u2014 to define the server-side template  <\/li>\n<li><strong>C# ViewModel<\/strong> \u2014 to define the data shape  <\/li>\n<li><strong>TypeScript Model<\/strong> \u2014 to mirror the data shape for the client  <\/li>\n<li><strong>Vue Component<\/strong> \u2014 to make the banner interactive (countdown logic)  <\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Six files, three languages, two rendering contexts. If the banner needs data from a headless CMS, add another REST client and another model conversion. If it needs form validation, add both C# server-side validation and JavaScript client-side validation \u2014 two implementations of the same rules.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In a SPA + BFF architecture, the first three layers disappear \u2014 but the remaining three do not. You still write the C# DTO, the TypeScript mirror, and the Vue component. You still write a REST client method. You still keep both sides in sync. The layer count drops from six to four, but the <strong>synchronization tax<\/strong> remains identical. Every field change still crosses the wire boundary by hand.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That is the cost of every feature. It compounds, and after a while developers start dreading change altogether.<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart TB\n    FEAT[\"Feature: Promotional banner&lt;br\/&gt;with countdown timer\"]\n\n    TAG[\"C# TagHelper\"]\n    VCMP[\"C# ViewComponent\"]\n    RAZOR[\"CSHTML Razor View\"]\n    VM[\"C# ViewModel\"]\n    TS[\"TypeScript model\"]\n    VUE[\"Vue component&lt;br\/&gt;(countdown logic)\"]\n\n    FEAT --&gt; TAG\n    FEAT --&gt; VCMP\n    FEAT --&gt; RAZOR\n    FEAT --&gt; VM\n    FEAT --&gt; TS\n    FEAT --&gt; VUE<\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The Deeper Problem: Architectural Coupling Disguised as Separation<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The irony is that these architectures <em>look<\/em> like they separate concerns. Server-side rendering is &#8220;separated&#8221; from client-side rendering. C# is &#8220;separated&#8221; from TypeScript. Razor views are &#8220;separated&#8221; from Vue components.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But the separation is structural, not functional. The layers remain tightly coupled through shared data contracts, rendering assumptions, and behavior expectations. Change one layer, and the ripple effects cross every boundary.<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart LR\n    subgraph Server[\"Server\"]\n        CS[\"C# Models\"]\n        RZ[\"Razor Views\"]\n    end\n\n    subgraph Client[\"Client\"]\n        VUE[\"Vue Components\"]\n    end\n\n    CS --&gt; RZ\n    RZ --&gt; VUE\n\n    subgraph Coupling[\"Shared assumptions \/ tight coupling\"]\n        FN[\"Field names must match\"]\n        DF[\"Date formats must match\"]\n        EV[\"Enum values must match\"]\n        NH[\"Null handling must match\"]\n    end\n\n    CS --- Coupling\n    RZ --- Coupling\n    VUE --- Coupling<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Real separation of concerns means a change in one area does not force changes in another. These architectures fail that test badly.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Breaking Out: The Architecture Used Instead<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The replacement architecture follows a different principle: <strong>one rendering engine, one type system, one unified API surface<\/strong>.<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart LR\n    subgraph Browser[\"Browser\"]\n        B[\"User\"]\n    end\n\n    subgraph Nuxt[\"Nuxt 4 (SSR)\"]\n        V3[\"Vue 3&lt;br\/&gt;Component Tree\"]\n        APClient[\"Apollo GraphQL Client\"]\n    end\n\n    subgraph Gateway[\"Apollo Server&lt;br\/&gt;(GraphQL Gateway)\"]\n    end\n\n    subgraph Backends[\"Backend Subgraphs\"]\n        CMS[\"CMS Subgraph\"]\n        DOTNET[\".NET Backend Subgraph\"]\n    end\n\n    B &lt;--&gt;|HTTP| Nuxt\n    Nuxt --&gt; V3\n    V3 --&gt; APClient\n    APClient --&gt; Gateway\n    Gateway --&gt; CMS\n    Gateway --&gt; DOTNET<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The key shifts:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Single rendering engine<\/strong>: Vue 3 renders on both server and client. The server produces HTML; the client hydrates the same component tree. No content jumps, no flickering.  <\/li>\n<li><strong>Single API surface<\/strong>: A GraphQL gateway stitches together the headless CMS and the .NET backend into one schema. The frontend writes <code>.graphql<\/code> query files; code generation produces fully typed TypeScript composables. No manual model translation.  <\/li>\n<li><strong>Single type system<\/strong>: TypeScript everywhere, with types generated from the GraphQL schema. A renamed field in the backend breaks the TypeScript compiler \u2014 not the production website at 2 AM.  <\/li>\n<\/ul>\n\n\n\n<pre class=\"mermaid\">sequenceDiagram\n    participant U as User (Browser)\n    participant N as Nuxt 4 SSR (Vue 3)\n    participant G as GraphQL Gateway\n    participant C as CMS Subgraph\n    participant D as .NET Subgraph\n\n    U-&gt;&gt;N: HTTP GET \/page\n    N-&gt;&gt;G: GraphQL query&lt;br\/&gt;(content + business data)\n    G-&gt;&gt;C: Resolve CMS fields\n    C--&gt;&gt;G: CMS data\n    G-&gt;&gt;D: Resolve .NET fields\n    D--&gt;&gt;G: .NET data\n    G--&gt;&gt;N: Unified GraphQL response\n    N--&gt;&gt;U: HTML (SSR) with embedded state\n    U-&gt;&gt;N: Hydration request (client-side)\n    N--&gt;&gt;U: Interactive Vue app&lt;br\/&gt;(no re-render jump)<\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">What Gets Eliminated<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">One of the most satisfying aspects of a migration like this is what you get to delete:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Layer<\/th><th>Old System<\/th><th>New System<\/th><\/tr><\/thead><tbody><tr><td><strong>C# ViewModels<\/strong><\/td><td>One per content type<\/td><td>\u274c Eliminated \u2014 types generated from GraphQL<\/td><\/tr><tr><td><strong>C# TagHelpers<\/strong><\/td><td>Custom HTML rendering<\/td><td>\u274c Eliminated \u2014 Vue components render everything<\/td><\/tr><tr><td><strong>CSHTML Razor Views<\/strong><\/td><td>Server-side templates<\/td><td>\u274c Eliminated \u2014 replaced by Vue SFC templates<\/td><\/tr><tr><td><strong>TypeScript model files<\/strong><\/td><td>Manual mirror of C# types<\/td><td>\u274c Eliminated \u2014 generated from schema<\/td><\/tr><tr><td><strong>REST client wrappers<\/strong><\/td><td>Per-endpoint fetch code<\/td><td>\u274c Eliminated \u2014 generated GraphQL composables<\/td><\/tr><tr><td><strong>JSON data injection<\/strong><\/td><td>Script tags with serialized models<\/td><td>\u274c Eliminated \u2014 SSR payload hydration<\/td><\/tr><tr><td><strong>Dual validation<\/strong><\/td><td>C# + JS validation rules<\/td><td>\u274c Eliminated \u2014 backend validation rules fetched at runtime<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Every row in that table represents hundreds of lines of code and hundreds of potential bugs \u2014 gone, not refactored.<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart TB\n    subgraph Legacy[\"Legacy layers\"]\n        L1[\"C# ViewModels\"]\n        L2[\"TagHelpers\"]\n        L3[\"CSHTML Razor Views\"]\n        L4[\"Manual TS models\"]\n        L5[\"REST client wrappers\"]\n        L6[\"JSON data injection\"]\n        L7[\"Dual validation logic\"]\n    end\n\n    subgraph New[\"New stack\"]\n        GQL[\"GraphQL schema\"]\n        CGEN[\"Code generation&lt;br\/&gt;(types + composables)\"]\n        VUE3[\"Vue 3 components&lt;br\/&gt;(SSR + CSR)\"]\n    end\n\n    L1 -.removed.-&gt; GQL\n    L2 -.removed.-&gt; VUE3\n    L3 -.removed.-&gt; VUE3\n    L4 -.removed.-&gt; CGEN\n    L5 -.removed.-&gt; CGEN\n    L6 -.removed.-&gt; VUE3\n    L7 -.moved.-&gt; GQL\n\n    GQL --&gt; CGEN --&gt; VUE3<\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Measured Results<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Architecture decisions are opinions until you measure them. After migrating a legacy ASP.NET MVC + Razor + Vue system to Nuxt 4 with a GraphQL gateway and load testing against production-equivalent traffic patterns:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Metric<\/th><th style=\"text-align:center\">Legacy System<\/th><th style=\"text-align:center\">New System<\/th><th style=\"text-align:center\">Change<\/th><\/tr><\/thead><tbody><tr><td><strong>Median response time<\/strong><\/td><td style=\"text-align:center\">2,618 ms<\/td><td style=\"text-align:center\">165 ms<\/td><td style=\"text-align:center\"><strong>15.9\u00d7 faster<\/strong><\/td><\/tr><tr><td><strong>Error rate<\/strong> (1\u00d7 prod load)<\/td><td style=\"text-align:center\">3.91%<\/td><td style=\"text-align:center\">0.09%<\/td><td style=\"text-align:center\"><strong>97% lower<\/strong><\/td><\/tr><tr><td><strong>Max capacity<\/strong><\/td><td style=\"text-align:center\">~99 RPM<\/td><td style=\"text-align:center\">494+ RPM<\/td><td style=\"text-align:center\"><strong>5\u00d7 more<\/strong><\/td><\/tr><tr><td><strong>Infrastructure<\/strong><\/td><td style=\"text-align:center\">3\u00d7 P3v3 fixed (24 vCPU, 96 GB)<\/td><td style=\"text-align:center\">Auto-scaled containers<\/td><td style=\"text-align:center\"><strong>Elastic<\/strong><\/td><\/tr><tr><td><strong>Lighthouse Performance<\/strong><\/td><td style=\"text-align:center\">~50<\/td><td style=\"text-align:center\">97+ (mobile)<\/td><td style=\"text-align:center\"><strong>Near-perfect<\/strong><\/td><\/tr><tr><td><strong>Developer layers per feature<\/strong><\/td><td style=\"text-align:center\">6 files, 3 languages<\/td><td style=\"text-align:center\">1\u20132 files, 1 language<\/td><td style=\"text-align:center\"><strong>80% less<\/strong><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">A 2.6-second median means the <em>better half<\/em> of requests still took 2.6 seconds \u2014 the slower half took even longer. A 165 ms median means the page is rendered before a user can blink. At 5\u00d7 production load (494 RPM), the new system held that 165 ms median flat. It did not degrade.<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart LR\n    subgraph Legacy[\"Legacy System\"]\n        LRt[\"Median response: 2,618 ms\"]\n        LEr[\"Error rate: 3.91%\"]\n        LCap[\"Capacity: ~99 RPM\"]\n        LInfra[\"3\u00d7 P3v3, fixed\"]\n    end\n\n    subgraph New[\"New System\"]\n        NRt[\"Median response: 165 ms\"]\n        NEr[\"Error rate: 0.09%\"]\n        NCap[\"Capacity: 494+ RPM\"]\n        NInfra[\"Auto-scaled containers\"]\n    end\n\n    Legacy --&gt; New<\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Lessons Learned<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Manual synchronization is the real enemy<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Whether you have a dual-rendering architecture (server HTML + client re-render) or a clean SPA that calls a BFF, the core problem is the same: <strong>humans maintaining parallel data contracts across a network boundary<\/strong>. In the dual-rendering case, you additionally suffer from rendering conflicts and layout shifts. But the SPA + BFF case is no less painful in the long run \u2014 every endpoint still demands a hand-written TypeScript interface, a bespoke REST client, manual error handling, and a quiet prayer that nobody renamed a field on the other side.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you are considering &#8220;progressive enhancement&#8221; by layering a JavaScript framework on top of server-rendered templates, be deliberate about the boundary. And if you already have a clean SPA but find yourself maintaining dozens of REST client wrappers and hand-mirrored types, recognize that the problem is not the rendering model \u2014 it is the absence of a <strong>single source of truth for data contracts<\/strong> that both sides can consume automatically.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Manual type translation does not scale<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Keeping C# models and TypeScript interfaces in sync is manageable at 5 types. At 50, it becomes a full-time job. At 100+, it is impossible without tooling. This applies equally whether those types back Razor views or REST endpoints consumed by a SPA \u2014 the synchronization burden is the same.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">OpenAPI-based code generation bridges the gap for REST APIs, and many teams use it successfully. But it only covers the REST surface. If the frontend also consumes a headless CMS, a SOAP service, or multiple microservices with their own contracts, those sources still need manual type definitions. And even with OpenAPI generation, the REST clients themselves often carry per-endpoint boilerplate: custom headers, auth token injection, error mapping, pagination, and cache invalidation logic that gets copy-pasted across dozens of service wrappers.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>A single schema that unifies all sources<\/strong> (for example, GraphQL) lets you generate types <em>and clients<\/em> for everything from one place. That is the difference between reducing boilerplate and eliminating it.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">You cannot buy your way out of a slow architecture<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Three P3v3 instances (24 vCPU, 96 GB RAM) running 24\/7 cannot match the performance of a few auto-scaled container replicas running a properly designed architecture. The response time improvement was not 10% or 20% \u2014 it was <strong>15.9\u00d7<\/strong>. That kind of gain does not come from tuning. It comes from removing fundamental bottlenecks in how requests are processed.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Infrastructure cost follows architecture<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Legacy systems tend to rely on fixed, always-on compute that costs the same regardless of traffic. A well-designed replacement auto-scales: 5 replicas at idle, 15 under load. Fixed infrastructure is either over-provisioned \u2014 wasting money \u2014 or under-provisioned \u2014 dropping requests. Elastic infrastructure tracks actual demand.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">What&#8217;s Next<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This article covered the <em>what<\/em> and <em>why<\/em>. The next article provides the architectural overview \u2014 the big picture that frames every subsequent deep dive:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Article 2<\/strong>: <em>The Target Architecture \u2014 A Bird&#8217;s-Eye View<\/em> \u2014 The four-container topology, the in-process GraphQL gateway, the code generation pipeline, and how every article in the series maps to the architecture.<\/li>\n<li><strong>Article 3<\/strong>: <em>GraphQL Schema Stitching \u2014 One API to Rule Them All<\/em> \u2014 How to unify multiple backend data sources into a single GraphQL gateway, including a custom <code>@delegate<\/code> directive for cross-subgraph field resolution.  <\/li>\n<li><strong>Article 4<\/strong>: <em>The @delegate Directive Deep Dive<\/em> \u2014 Cross-subgraph field resolution with typed placeholders and formatters.  <\/li>\n<li><strong>Article 5<\/strong>: <em>GraphQL-Based Code Generation \u2014 Eliminating All Boilerplate<\/em> \u2014 How writing a <code>.graphql<\/code> file generates a fully typed Vue composable with zero manual work.  <\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Each article focuses on a specific pattern and shows how automation can eliminate entire categories of manual work.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Munir Husseini is a software architect specializing in full-stack TypeScript, .NET, and cloud-native architectures.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>First in a series about migrating from legacy web architectures to a modern Nuxt 4 stack. The series covers architecture, code generation, performance, infrastructure, and the automation philosophy behind every decision. The Starting Point: Architectures That Work \u2014 But Barely Across multiple large, high-traffic, customer-facing web platforms \u2014 the kind where millions of visitors per [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":210,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[5],"tags":[],"class_list":["post-161","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-advanced-web-app-with-nuxt-and-net"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.7 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>The Legacy Problem \u2014 When Manual Synchronization Becomes the Architecture - Software Production<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/softwareproduction.eu\/?p=161\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"The Legacy Problem \u2014 When Manual Synchronization Becomes the Architecture - Software Production\" \/>\n<meta property=\"og:description\" content=\"First in a series about migrating from legacy web architectures to a modern Nuxt 4 stack. The series covers architecture, code generation, performance, infrastructure, and the automation philosophy behind every decision. The Starting Point: Architectures That Work \u2014 But Barely Across multiple large, high-traffic, customer-facing web platforms \u2014 the kind where millions of visitors per [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/softwareproduction.eu\/?p=161\" \/>\n<meta property=\"og:site_name\" content=\"Software Production\" \/>\n<meta property=\"article:published_time\" content=\"2026-06-06T21:46:58+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-06-07T01:21:27+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/01-the-legacy-problem.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1880\" \/>\n\t<meta property=\"og:image:height\" content=\"1253\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Munir Husseini\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Munir Husseini\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"17 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=161#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=161\"},\"author\":{\"name\":\"Munir Husseini\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#\\\/schema\\\/person\\\/fec48f54713e1bd117640fb9b748802f\"},\"headline\":\"The Legacy Problem \u2014 When Manual Synchronization Becomes the Architecture\",\"datePublished\":\"2026-06-06T21:46:58+00:00\",\"dateModified\":\"2026-06-07T01:21:27+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=161\"},\"wordCount\":2536,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=161#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/01-the-legacy-problem.jpg\",\"articleSection\":[\"Advanced Web App with Nuxt and .NET\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/softwareproduction.eu\\\/?p=161#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=161\",\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=161\",\"name\":\"The Legacy Problem \u2014 When Manual Synchronization Becomes the Architecture - Software Production\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=161#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=161#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/01-the-legacy-problem.jpg\",\"datePublished\":\"2026-06-06T21:46:58+00:00\",\"dateModified\":\"2026-06-07T01:21:27+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=161#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/softwareproduction.eu\\\/?p=161\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=161#primaryimage\",\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/01-the-legacy-problem.jpg\",\"contentUrl\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/01-the-legacy-problem.jpg\",\"width\":1880,\"height\":1253},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=161#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/softwareproduction.eu\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"The Legacy Problem \u2014 When Manual Synchronization Becomes the Architecture\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#website\",\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/\",\"name\":\"Softwareproduction\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/softwareproduction.eu\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#organization\",\"name\":\"Munir Husseini\",\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/softwareproduction-logo-32.png\",\"contentUrl\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/softwareproduction-logo-32.png\",\"width\":32,\"height\":32,\"caption\":\"Munir Husseini\"},\"image\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#\\\/schema\\\/logo\\\/image\\\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#\\\/schema\\\/person\\\/fec48f54713e1bd117640fb9b748802f\",\"name\":\"Munir Husseini\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g\",\"caption\":\"Munir Husseini\"},\"sameAs\":[\"https:\\\/\\\/softwareproduction.eu\\\/\"],\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/?author=1\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"The Legacy Problem \u2014 When Manual Synchronization Becomes the Architecture - Software Production","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/softwareproduction.eu\/?p=161","og_locale":"en_US","og_type":"article","og_title":"The Legacy Problem \u2014 When Manual Synchronization Becomes the Architecture - Software Production","og_description":"First in a series about migrating from legacy web architectures to a modern Nuxt 4 stack. The series covers architecture, code generation, performance, infrastructure, and the automation philosophy behind every decision. The Starting Point: Architectures That Work \u2014 But Barely Across multiple large, high-traffic, customer-facing web platforms \u2014 the kind where millions of visitors per [&hellip;]","og_url":"https:\/\/softwareproduction.eu\/?p=161","og_site_name":"Software Production","article_published_time":"2026-06-06T21:46:58+00:00","article_modified_time":"2026-06-07T01:21:27+00:00","og_image":[{"width":1880,"height":1253,"url":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/01-the-legacy-problem.jpg","type":"image\/jpeg"}],"author":"Munir Husseini","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Munir Husseini","Est. reading time":"17 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/softwareproduction.eu\/?p=161#article","isPartOf":{"@id":"https:\/\/softwareproduction.eu\/?p=161"},"author":{"name":"Munir Husseini","@id":"https:\/\/softwareproduction.eu\/#\/schema\/person\/fec48f54713e1bd117640fb9b748802f"},"headline":"The Legacy Problem \u2014 When Manual Synchronization Becomes the Architecture","datePublished":"2026-06-06T21:46:58+00:00","dateModified":"2026-06-07T01:21:27+00:00","mainEntityOfPage":{"@id":"https:\/\/softwareproduction.eu\/?p=161"},"wordCount":2536,"commentCount":0,"publisher":{"@id":"https:\/\/softwareproduction.eu\/#organization"},"image":{"@id":"https:\/\/softwareproduction.eu\/?p=161#primaryimage"},"thumbnailUrl":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/01-the-legacy-problem.jpg","articleSection":["Advanced Web App with Nuxt and .NET"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/softwareproduction.eu\/?p=161#respond"]}]},{"@type":"WebPage","@id":"https:\/\/softwareproduction.eu\/?p=161","url":"https:\/\/softwareproduction.eu\/?p=161","name":"The Legacy Problem \u2014 When Manual Synchronization Becomes the Architecture - Software Production","isPartOf":{"@id":"https:\/\/softwareproduction.eu\/#website"},"primaryImageOfPage":{"@id":"https:\/\/softwareproduction.eu\/?p=161#primaryimage"},"image":{"@id":"https:\/\/softwareproduction.eu\/?p=161#primaryimage"},"thumbnailUrl":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/01-the-legacy-problem.jpg","datePublished":"2026-06-06T21:46:58+00:00","dateModified":"2026-06-07T01:21:27+00:00","breadcrumb":{"@id":"https:\/\/softwareproduction.eu\/?p=161#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/softwareproduction.eu\/?p=161"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/softwareproduction.eu\/?p=161#primaryimage","url":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/01-the-legacy-problem.jpg","contentUrl":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/01-the-legacy-problem.jpg","width":1880,"height":1253},{"@type":"BreadcrumbList","@id":"https:\/\/softwareproduction.eu\/?p=161#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/softwareproduction.eu\/"},{"@type":"ListItem","position":2,"name":"The Legacy Problem \u2014 When Manual Synchronization Becomes the Architecture"}]},{"@type":"WebSite","@id":"https:\/\/softwareproduction.eu\/#website","url":"https:\/\/softwareproduction.eu\/","name":"Softwareproduction","description":"","publisher":{"@id":"https:\/\/softwareproduction.eu\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/softwareproduction.eu\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/softwareproduction.eu\/#organization","name":"Munir Husseini","url":"https:\/\/softwareproduction.eu\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/softwareproduction.eu\/#\/schema\/logo\/image\/","url":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/05\/softwareproduction-logo-32.png","contentUrl":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/05\/softwareproduction-logo-32.png","width":32,"height":32,"caption":"Munir Husseini"},"image":{"@id":"https:\/\/softwareproduction.eu\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/softwareproduction.eu\/#\/schema\/person\/fec48f54713e1bd117640fb9b748802f","name":"Munir Husseini","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g","caption":"Munir Husseini"},"sameAs":["https:\/\/softwareproduction.eu\/"],"url":"https:\/\/softwareproduction.eu\/?author=1"}]}},"jetpack_featured_media_url":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/01-the-legacy-problem.jpg","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/posts\/161","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=161"}],"version-history":[{"count":3,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/posts\/161\/revisions"}],"predecessor-version":[{"id":211,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/posts\/161\/revisions\/211"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/media\/210"}],"wp:attachment":[{"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=161"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=161"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=161"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}