The Full Picture — Was the Machinery Worth Owning?
Hero image credit: Photo by Sonny Sixteen on Pexels
Part 20 and the conclusion of the Nuxt and .NET series.
Part 1 opened with a change request: show a document’s revision number next to its title. It took six coordinated edits in a dual-rendering application, none of them checked by anything. Eighteen months of migration work later, the honest question is not whether the new architecture is more modern. It is whether that change got cheaper by enough to justify everything now in the repository.
This article answers that, collects the lessons that turned out to be transferable, and says where I would not build this again.
Table of Contents
- The Revision Label Eighteen Months Later
- A Logical View of the System
- Four Decisions That Got an Owner
- Three Rules That Kept Reappearing
- What It Costs to Own
- What Can Be Claimed Responsibly
- When to Adopt Simplify or Decline
- The Series in Reading Order
- Closing Thoughts
The Revision Label Eighteen Months Later
Same request, current architecture. The page’s GraphQL operation selects revision alongside title, and the component renders it. Generation updates the operation’s result type; the component either type-checks against it or the build fails. If the field does not exist in the schema, operation validation fails. If it is nullable and the component treats it as a number, the type check fails at the usage.
Part 1 proposed counting two things for a change like this: how many places must agree, and how many of those agreements are verified automatically. The count is now two edits with both agreements checked, against six edits with none. The ratio is what moved, and that framing has held up better than any of the technology choices in this series — because it tells you what to look at in a codebase you have never seen.
What did not get cheaper is worth the same precision. The field still has to exist in the .NET API and be exposed through the schema, which is a backend change with its own review. The checks only tell the truth when generation runs against the current schema in CI. Nullability, authorization and validation remain the API’s job, because a generated type enforces nothing at runtime. The change became local and checkable; it did not become free.
A Logical View of the System
Logical responsibilities, not a container inventory. The cache may be a container in an isolated preview and a managed service in production; the proxy forwards requests and sets cache policy without holding responses; whether the CDN caches anything depends on its configuration and the response semantics.
Four Decisions That Got an Owner
Client data contracts are derived rather than transcribed. The gateway composes one schema, and the client’s types come from it. The general principle is schema-derived contracts over manually synchronized ones — not GraphQL over REST, since OpenAPI-generated clients provide the same property for REST APIs. The qualification, stated once more because it is the one people drop: generated code is trustworthy only when it is regenerated against the current schema and the type check actually runs.
Rendering has one owner. One component tree renders on the server and hydrates in the browser, so there is no second template language and no second data representation. The cost is a contract: the browser must start from the state the server used, which is a discipline rather than a feature.
Module boundaries are declared and checkable. Each module states what it registers, owns its configuration, and depends only in permitted directions. Nuxt modules do not make files private, so the boundary is exactly as strong as the lint rule, package export or review that enforces it. Knowing which of those you have is more useful than believing you have encapsulation.
Releases are verified before they receive traffic. Configuration is generated from one source per environment, a candidate deployment revision is exercised at its own address, and traffic switches at the proxy. None of that isolates shared data, cache formats, external side effects or browser clients from the previous release — which is why the compatibility rules matter more than the switching mechanism.
Three Rules That Kept Reappearing
Writing these twenty articles surfaced three rules that each showed up in four or five different contexts. They are the part I would carry to a different stack.
Anything reused must be keyed by everything that varies it. Cache identity in the gateway, the per-URL result set for CMS pages, the useAsyncData key, the experiment assignment in a page cache, a DataLoader’s scope. Every one of those is the same rule, and every violation produces the same category of incident: one visitor’s content served to another, or one variant’s page served to everybody.
Any value the server decided must be transferred, not recomputed. Experiment assignment, async condition results, validity flags, locale, authorization outcomes. Recomputing in the browser produces hydration mismatches when it disagrees and wasted work when it agrees. The corollary is equally useful: a value that genuinely cannot be known on the server belongs behind an explicit client boundary, not in a shared code path that hopes.
A boundary exists only if something checks it. Module imports, contract validation in CI, release gates, cache-key correctness, hydration warnings failing a browser test. Every boundary in this architecture that lacked a check eventually drifted, and every one that had a mechanical check stayed where it was put. The uncomfortable implication is that architecture documents are not architecture; the lint rules, generators and gates are.
What It Costs to Own
- Generators need tests, ownership and an upgrade path, and they fail in a specific nasty way: silently producing stale output that compiles.
- A stitched gateway is a component with a schema lifecycle, caching semantics, failure policy and its own diagnostics.
- SSR requires memory budgeting and request-scoped state discipline, and punishes violations under concurrency rather than in development.
- Preview environments need reconciliation-based cleanup, access restriction, and awareness of the shared dependencies they are not isolating.
- Candidate releases consume capacity, and retained revisions constrain secret removal and cache-format changes.
- Observability needs bounded label cardinality, redaction, sampling policy and retention rules, or it becomes a cost centre that also leaks data.
And one cost that is easy to leave off the list: all of this needs people who understand it. Each mechanism above replaced repetitive work with a system that has its own failure modes, and a team that inherits the system without the reasoning will reasonably conclude that the reasoning does not exist.
What Can Be Claimed Responsibly
| Area | Defensible benefit | Necessary qualification |
|---|---|---|
| Data access | Less handwritten client plumbing, earlier contract errors | Requires current schemas, regeneration and a real type check |
| Rendering | Useful HTML before client startup | Adds server work and a hydration contract |
| Caching | Fewer repeated upstream operations | Keys and invalidation must respect user, locale and variant identity |
| Deployment | Candidate verification before cutover | Shared state and older clients still need compatibility rules |
| Recovery | Previous release available without rebuilding | Readiness and routing propagation affect actual recovery time |
| Diagnostics | Better evidence for a root-cause hypothesis | Sampling, overhead, permissions and retention all constrain it |
On performance, the position from Part 19 stands: the observations span seconds-scale response summaries for the legacy stack and hundreds of milliseconds for many requests in the revised one, and the revised application handled several multiples of a chosen baseline under declared conditions. Those are ranges and tested behaviour. They are not a like-for-like whole-system ratio, because the historical aggregates and the new measurements were taken at different boundaries over different populations — and inventing a ratio from them would undermine everything else in this series.
When to Adopt Simplify or Decline
The architecture is not the recommendation. The recommendation is to match the machinery to the problem, and Part 1’s diagnostic is the test.
Adopt something like this when a small change touches many unchecked agreements, when pages genuinely combine several independently owned data sources, when server rendering is a requirement rather than a preference, and when the team is large enough that boundaries need to be enforced by tools rather than by conversation. Those four conditions together are what make generators and a gateway cheaper than the coordination they replace.
Simplify it when one of those falls away. A single backend API needs no stitching gateway — generate a client from its specification and keep the composition in the frontend. A site with no editorial content needs no catch-all route, no content generators and no conditional-visibility system. A team of three can enforce module boundaries in review and skip the packaging.
Decline it when the application is small, its content is largely static, or its change volume is low. Most of this series’ mechanisms are amortized costs: they pay off across many changes and are pure overhead across few. A brochure site with a contact form and one integration is better served by the simplest thing that renders it, and choosing that is not a failure of ambition.
The Series in Reading Order
Foundations and data contracts
- The Legacy Problem
- The Target Architecture
- GraphQL Schema Stitching
- The Custom Delegate Directive
- GraphQL-Based Code Generation
Application structure and content
- Custom Nuxt Modules
- GraphQL Toolkit and Typed i18n
- The Compose Pattern
- Nuxt and a Headless CMS
- Conditional Content and Live Preview
- A/B Testing at the SSR Level
Rendering and browser performance
Security, operations, and verification
- Security in a Nuxt SSR App
- Configuration Generation and Safe Releases
- Memory, Stability, and PM2
- Tracing, Logging, and Process Diagnostics
- Load Testing an SSR Migration
- The Full Picture — this article.
Closing Thoughts
If I had to compress eighteen months into one sentence, it would be that the valuable architectural changes were the ones that turned an agreement somebody had to remember into an agreement a machine could check. Not the frameworks, not the generators as such — the checks.
That is also the limit. The checks reduce repeated work and make failures easier to locate; they do not remove drift, outages or operational cost, and each one is a thing you now own. So keep the claims narrow enough to test, the examples free of anything that identifies a customer, and the measurements comparable enough to be worth quoting. An architecture you can describe honestly is usually the one you can still change.
Munir Husseini is a software architect specializing in full-stack TypeScript, .NET, and cloud-native architectures.
Category: Advanced Web App With Nuxt And Net