Twentieth and final article in a series about migrating from legacy architectures to a modern Nuxt 4 stack.
From Parts to Whole
The previous fifteen articles describe individual pieces — the GraphQL gateway, code generators, performance, infrastructure, security. This article brings them together and answers the question that matters to decision-makers: what does the complete system deliver?
The Architecture at a Glance
flowchart TB
A["Azure Front Door<br/>(CDN + WAF)"] --> B[Azure Container Apps Environment]
B --> C["Nginx Proxy<br/>TLS, Image cache, OTel spans"]
C --> D["Nuxt 4<br/>SSR + GQL Gateway<br/>SSR, GraphQL stitching<br/>Page cache, Redis"]
D --> E[".NET API<br/>Pricing, Orders, Users, Validation"]
D --> F["Redis<br/>(per-env)"]
B --> G["External Services<br/>Headless CMS · Application Insights<br/>Azure Key Vault · Azure AD"]
Four containers per environment. One frontend language (TypeScript). One data schema (GraphQL). One module system (Nuxt modules). One configuration generator (YAML → JSON + Bicep).
The Five Pillars
Five architectural pillars, each delivering measurable value in a large enterprise application.
Pillar 1: Unified Data Layer (GraphQL Schema Stitching)
What it delivers: One API endpoint for all data sources. The frontend never needs to know which backend produced which field.
| Before | After |
|---|---|
| 3–5 REST calls per page | 1 GraphQL query per page |
| Manual data joining in frontend code | Automatic via @delegate directive |
| Per-endpoint types, manually maintained | Generated from unified schema |
| Custom error handling per API | One Apollo error link |
Pillar 2: Total Automation (Code Generation)
What it delivers: Developers write declarations (GraphQL queries, YAML translations, GraphQL input types). The system generates everything else.
flowchart LR
A[What developers write] --> B[What is generated]
A1[.graphql files] --> B1["Typed composables<br/>(auto-imported)"]
A2[YAML translation files] --> B2["Typed t.* proxy chain<br/>(auto-imported)"]
A3[GraphQL input types] --> B3[Form field metadata + validation]
A4[CMS content model] --> B4[Vue component stubs + types]
A5[Module scaffold command] --> B5[Complete module structure]
Roughly 40–60% of the TypeScript code is generated — not boilerplate, but correct implementations derived from authoritative schemas.
Pillar 3: SSR Performance Stack
What it delivers: Near-instant page loads with near-perfect Lighthouse scores.
flowchart TB
S[Performance Stack]
S --> L1["SSR<br/>Content visible immediately"]
S --> L2["Multi-Tier Cache<br/>Sub-ms data retrieval"]
S --> L3["Deferred Hydration<br/>No render-blocking JS"]
S --> L4["Same-Origin Proxy<br/>−594ms LCP"]
S --> L5["Font Strategy<br/>Zero CLS"]
S --> L6["Bot Detection<br/>Clean audit scores"]
S --> L7["Manual Chunks<br/>Only needed JS per page"]
S --> R["Combined Result<br/>Lighthouse 97+ (mobile)"]
Pillar 4: Production-Grade Operations
What it delivers: Elastic infrastructure, zero-downtime deployments, full observability, and instant rollback.
| Capability | Implementation | Article |
|---|---|---|
| Elastic scaling | Container Apps auto-scale (5–20 replicas) | 11 |
| Zero-downtime deploy | Blue-green with traffic switching | 11 |
| Per-branch environments | Automated feature deployments | 11 |
| Full request tracing | W3C Trace Context across all services | 13 |
| Security | CSRF + Azure AD + runtime CSP | 12 |
| Instant rollback | Traffic switch to previous revision | 11 |
Pillar 5: Developer Experience
What it delivers: Fast feedback loops, strong type safety, AI-assisted debugging, and clear modular boundaries.
| Aspect | Experience |
|---|---|
| New data source | Write .graphql file → composable auto-imported |
| New translation | Add YAML key → t.section.key typed and available |
| New module | Run scaffold → complete structure created |
| Debugging | AI assistant with 30+ live inspection tools |
| Architecture understanding | AGENTS.md + module READMEs + consistent patterns |
For Decision-Makers: The Numbers
Performance
These example metrics illustrate typical gains when moving a legacy enterprise frontend to a modern SSR + GraphQL stack:
| Metric | Legacy | New | Improvement |
|---|---|---|---|
| Median response time | 2,618 ms | 165 ms | 15.9× faster |
| Error rate | 3.91% | 0.09% | 97% lower |
| Lighthouse Performance (mobile) | ~50 | 97+ | +47 points |
| LCP | > 5 s | < 2.5 s | Google “good” |
Capacity and Cost
| Metric | Legacy | New | Improvement |
|---|---|---|---|
| Max tested capacity | ~99 RPM | 494+ RPM | 5× more |
| Infrastructure model | Fixed (always on) | Elastic (pay-per-use) | ~40% cost reduction at average load |
| Scaling | Manual (operations team) | Automatic (config-driven) | Zero manual intervention |
Development Velocity
| Metric | Legacy | New | Improvement |
|---|---|---|---|
| New API integration | Write REST client + types + mapper | Write .graphql file | ~90% less glue code |
| New form | Backend + frontend sync + manual testing | Schema-driven, auto-validated | No manual field wiring |
| New CMS content type | Manual component + data fetching + types | Generated component stub + typed query | No boilerplate |
| New translation key | Add key, hope it matches at runtime | YAML key → typed t.section.key | Compile-time checked |
| New module | Create folders, wire routing, exports, types | yarn plop → complete scaffold | Consistent structure in seconds |
| Type safety coverage | Partial (hand-written) | Complete (generated) | Zero type drift |
For Architects: The Design Principles
Five design principles unify the decisions across all 15 articles for large-scale web applications.
1. Generate, Don’t Write
If code can be derived from an authoritative schema (GraphQL, CMS model, YAML translations), generate it. Hand-written code drifts; generated code stays correct.
2. One Source of Truth Per Concern
- Data shape → GraphQL schema
- Validation rules → Backend model annotations
- URL structure → CMS entries
- Translations → YAML files
- Infrastructure configuration → YAML values files
No concern has two sources. Everything is defined once and consumed many times.
3. Eliminate Work, Don’t Optimize It
The biggest performance gains came from removing work: modulepreload hints, cross-origin connections, redundant CMS queries. The biggest productivity gains came from removing boilerplate. Subtraction outperforms optimization.
4. Modules as Boundaries
Folders suggest organization. Modules enforce it. With 35+ modules, each owning its own API surface, the codebase has real boundaries.
5. Measure Everything, Assume Nothing
Architecture claims without data are opinions. Load test results, Lighthouse scores, cache hit rates, and response time distributions turn them into evidence. The production configuration is shaped by measurement.
Lessons Learned
The whole is greater than the sum of the parts
No single technique produces a 15.9× improvement. Stitched GraphQL removes multi-source joins, caching eliminates redundant fetches, deferred hydration removes render-blocking JS, and elastic infrastructure eliminates over-provisioning. Each targets a different bottleneck; together they transform the system.
Architecture must allow change
The architecture is designed upfront — but designed for adaptability. Technology and requirements evolve, and the system must evolve with them. An architecture that requires wholesale rewrites accumulates debt until it becomes unmaintainable.
Four mechanisms make change safe:
- Loose coupling via modules. Dozens of independent modules, each owning a vertical slice. Replacing authentication, tracking, or forms never cascades into unrelated code. A module can be deleted and rebuilt without touching the rest.
- Code generation from schemas. Generated code follows the schema it came from. When a data source changes or a service is replaced, regeneration produces correct integration code. No hand-written adapter layer drifting from reality.
- Schema-driven contracts (GraphQL). The frontend depends on a unified schema, not individual backends. A service can be rewritten, split, or replaced — as long as it serves the same fields, no frontend changes are required. New clients (mobile, internal tools) consume the same gateway without backend modifications.
- Infrastructure as configuration. The same YAML generates manifests for Container Apps, AKS, and App Service. Switching platforms or adding environments is a configuration change, not a re-architecture.
In practice: adding a data source is one subgraph and a stitch directive. Changing rendering strategy is a route rule. Replacing a backend service is transparent to the frontend. Swapping a module is contained to its directory.
Things that change often (services, modules, data sources, infrastructure) are easy to change. The few core decisions that are hard to change (framework, data protocol) are chosen carefully and affect only their own layer.
Developer experience is a force multiplier
Fast feedback loops (auto-generated types, branch environments, AI debugging) do not just improve morale — they improve the system. When adding a data source takes minutes instead of hours, teams integrate more data. When debugging takes minutes instead of days, bugs get fixed faster.
Developer hardware is not optional
This architecture demands fast machines. The dev stack runs a Nuxt server watching thousands of files, a GraphQL server, code generators, and TypeScript language services analyzing the module graph — simultaneously. When hardware falls short, HMR becomes sluggish, type checking lags, and code generation feels blocking.
Windows is particularly affected. Node.js file watching and module resolution are measurably slower on NTFS than on macOS or Linux. Teams on Windows need WSL2, faster disks, or higher-spec hardware.
A trade-off worth noting: the architecture optimizes for velocity given adequate hardware. The minimum spec is higher than simpler stacks. Budget accordingly.
What’s Next
The remaining articles dive deeper into specific technical patterns:
- Article 17: The
@delegateDirective Deep Dive — Cross-subgraph field resolution in detail - Article 18: Building a Headless Design System — The Compose Pattern — Separating style logic from templates
- Article 19: A/B Testing at SSR Level — Cookie-based variant selection during server rendering
- Articles 20–27: Content preview, logging, module development, conditional rendering, image proxying, observability, reactive filters, and deferred hydration
Munir Husseini is a software architect specializing in full-stack TypeScript, .NET, and cloud-native architectures.
Leave a Reply