02 Architecture Overview
The Target Architecture — A Bird’s-Eye View
Hero image credit: Photo by REFARGOTOHP on Pexels
Second in a series about migrating from legacy architectures to a modern Nuxt 4 stack. This article provides the architectural overview that frames every subsequent article in the series.
Table of Contents
- From Problem to Blueprint
- The Four Containers
- The Nuxt 4 SPA — Where Most of the Magic Lives
- The Code Generation Pipeline
- The Module System — Dozens of Boundaries
- The Request Lifecycle
- The Infrastructure Model
- What This Architecture Delivers — A Preview
- The Key Architectural Decisions
- What’s Next
From Problem to Blueprint
The previous article exposed the problem: manual synchronization across rendering layers, fragile data contracts, and performance that no amount of hardware can fix. This article maps the complete target architecture end-to-end — every major component, why it exists, and which article dives deeper into it. Think of it as the map before the journey.
The Four Containers
The entire application runs as four containers inside a managed container environment (Azure Container Apps or Kubernetes). Each container has a single, clear responsibility:
Why Four Containers?
| Container | Why it exists |
|---|---|
| Nginx Proxy | Eliminates cross-origin penalties, serves cached images and assets at the edge, adds distributed tracing spans, and keeps TLS configuration out of the application code. |
| Nuxt 4 SPA | The heart of the system — renders HTML via SSR, runs the GraphQL gateway in-process, and serves the hydrated Vue 3 client application. |
| .NET API | Owns business logic that cannot live in Node.js — transactional operations, pricing calculations, and form validation rule definitions. Calls internal and external services (offer engine, order processing, payment, CRM). As a separate, non-internet-facing container it can be configured with stricter security policies. |
| Redis | Coordinates cache state across multiple SPA replicas and stores page-level response caches. Each environment (and each feature branch) gets its own Redis instance to prevent cross-contamination. |
The Nuxt 4 SPA — Where Most of the Magic Lives
The SPA container is far more than a frontend renderer. It is a full-stack TypeScript application powered by Nuxt 4’s Nitro server engine:
The In-Process GraphQL Gateway
This is the single most important architectural decision: the Apollo Server that stitches together all data sources runs inside the same Node.js process as the Nuxt application.
During SSR, when a Vue component needs data, the call path is:
Vue component → useAsyncData → Apollo Client → Apollo Server → Subgraph resolver
That entire chain is a function call — no HTTP, no serialization, no network latency. The GraphQL gateway is just another module in the Nuxt process. On the client side, after hydration, the same gateway is reachable via HTTP at /api/graphql, but the server-rendered page was already produced without a single network hop for data.
Schema Stitching In-Process
At startup, the gateway fetches the GraphQL schemas of all connected subgraphs (CMS, .NET API) and merges them into a single unified schema. The frontend writes one GraphQL query that can span data from any source:
query ProductPage($path: String!, $zip: String!) {
# Resolved from CMS subgraph
page(path: $path) {
title
heroImage { url }
body { json, links { ... } }
}
# Resolved from .NET API subgraph
offers(postalCode: $zip) {
name
monthlyPrice
features
}
}
One query. One response. One set of generated TypeScript types. The frontend never needs to know which backend produced which field.
The Code Generation Pipeline
Code generation is load-bearing infrastructure. Approximately 40–60% of the TypeScript code in the application is generated, not hand-written.
What Gets Generated
| Input | Generator | Generated Output | Manual Work Eliminated |
|---|---|---|---|
.graphql files + schema |
GraphQL Codegen | TypeScript types, rich text fragments, possibleTypes maps |
TS interface authoring, cache misconfiguration, copy-paste fragments |
.graphql files |
GraphQL Toolkit Module | Vue composables (useXxxQuery, useXxxClient) |
REST client, mapper, error handling |
| YAML translation file | Typed i18n Module | Typed t.section.key proxy chain |
Runtime key lookups, missing-key bugs |
| GraphQL schema | GraphQL Codegen | Component stubs + GraphQL queries (entry + collection per type) | Boilerplate components, manual query writing |
| GraphQL input types | Introspection Forms | Type-safe, validatable form components + field metadata | Manual field wiring, dual validation |
| YAML config files (per env) | Config Generator | Pipeline variable files, Bicepparam files, ACA container manifests | Manual env-specific file maintenance, drift between environments |
| Modules, pages, components + nuxt.config.ts | Nuxt Build | Full TS app scaffold, Vite config, auto-imports, lazy component wrappers | Manual imports, route-level code splitting, build config wiring |
The developer workflow becomes: write a declaration, run the code generator, get a fully typed implementation. The TypeScript compiler then validates everything against the schema — a renamed field produces a compile error, not a production bug.
The Module System — Dozens of Boundaries
The application is not a monolith with folders. It is a composition of dozens of Nuxt modules, each owning a vertical slice of functionality. An enterprise website of this scale inevitably needs modules across several categories:
The exact number of modules depends on the project’s requirements, but enterprise applications commonly end up with dozens. Each module:
- Has a clear public API (exported composables, components)
- Owns its server middleware, plugins, and handlers
- Is independently documentable (each has a README)
- Can be added or removed without ripple effects across the codebase
These are real architectural boundaries enforced by Nuxt’s module system. The categories above are illustrative; a real project will have modules for cookie consent, third-party widget integration, feature flags, payment flows, and many other concerns that only surface once the application reaches production scale.
The Request Lifecycle
Here is what happens when a user requests a page, end-to-end:
Key points:
- Zero network overhead for SSR data: The GraphQL gateway is in-process
- Data caching, not HTML caching: CMS content entries are cached per path in Redis; HTML is rendered fresh every request because A/B tests, user-specific conditions, and other factors may differ
- Multi-tier caching: Redis → in-memory LRU → per-operation cache (CMS data only; business data stays real-time)
- Single roundtrip: One GraphQL query fetches all data for a page
- Instant interactivity: Hydration attaches to existing DOM, no re-render
The Infrastructure Model
The container environment scales elastically and supports three deployment tiers:
Everything is generated from YAML configuration files. Adding a new environment variable means defining it once — the generator produces the correct manifests, parameters, and pipeline variables for every environment and platform combination.
What This Architecture Delivers — A Preview
Before diving into the design rationale, here is what the combined effect of these decisions looks like under real production load:
| Metric | Result |
|---|---|
| Median response time | 165 ms vs 2,618 ms — 15.9× faster |
| Error rate under load | 0.09% vs 3.91% — 97% lower |
| Max tested capacity | 494+ RPM vs ~99 RPM — 5× more |
| Lighthouse Performance (mobile) | 97–100 — up 47 points |
| Infrastructure cost | ~40% lower — elastic scaling vs fixed instances |
All of these are measured results from load testing against production-equivalent traffic patterns. Article 19 covers the full methodology and breakdown. The rest of this series explains how and why each architectural layer contributes to these numbers.
The Key Architectural Decisions
Five decisions define this architecture. Every other choice flows from them:
1. SSR with In-Process Gateway
The GraphQL gateway runs inside Nuxt, not as a separate service. This eliminates an entire network hop during server rendering and reduces infrastructure complexity from five services to four containers.
2. Schema Stitching Over Federation
Schema stitching works with any GraphQL endpoint — including third-party CMS APIs you cannot modify. Federation requires control over all subgraphs. Stitching was the only option that fit the real-world constraints.
3. Code Generation as Load-Bearing Infrastructure
Generated code is the production code. The .graphql files are the source; the generated TypeScript is the implementation. This matters because generated types are always in sync with the schema — a renamed field in the backend becomes a TypeScript compiler error, not a undefined at runtime caught by a customer at 2 AM. Approximately 40–60% of the TypeScript codebase is generated, not hand-written.
4. Modules as Architectural Boundaries
Each module owns its composables, components, server handlers, and types. Cross-module communication happens through defined interfaces (hooks, event bus, shared state). This matters because folders are suggestions — nothing prevents a file in components/ from importing internals from services/checkout/. Nuxt modules enforce the boundary structurally: what is not exported is not accessible.
5. Configuration Generation Over Management
Infrastructure configuration is not managed by hand. YAML files define the truth; a generator produces Container Apps manifests, Bicep parameters, and pipeline variables. This eliminates an entire class of deployment bugs — the kind where a variable defined in the test manifest was never added to the production one, discovered at the worst possible moment.
What’s Next
With the architecture mapped out, the following articles dive into each component in detail:
- Article 3: GraphQL Schema Stitching — One API to Rule Them All — How the in-process gateway merges multiple data sources into a single schema.
- Article 4: The @delegate Directive Deep Dive — Cross-Subgraph Field Resolution — Typed placeholders and formatters for cross-subgraph queries.
- Article 5: GraphQL-Based Code Generation — Eliminating All Boilerplate — How
.graphqlfiles become fully typed Vue composables.
Each article builds on the mental model established here. When you encounter “the gateway resolves this field from the CMS subgraph,” you will know exactly where that gateway lives, how it connects, and why the design works the way it does.
Munir Husseini is a software architect specializing in full-stack TypeScript, .NET, and cloud-native architectures.
Hero image credit: Photo by REFARGOTOHP on Pexels
Category: Advanced Web App With Nuxt And Net