knext

SCS / Zones

Self-Contained Systems and Multi-Zones on knext — the architecture, the hard rules, and what's recipe-level today.

knext's target architecture is a Self-Contained Systems (SCS) + Next.js Multi-Zones model: a product is split into autonomous vertical slices ("zones"), each owning its UI, logic, and data, each deployed as its own scale-to-zero Knative Service. The Overview covers the why; this page is the reference for the rules.

Honest scope. Much of the SCS/PWA capability described here is the end goal, not shipping framework code today. Where a capability is "recipe-level" (an app-level template, not built into knext core), it is marked as such. Never treat an unbuilt generator as shipping.

What a zone is

A zone is one Self-Contained System: an autonomous Next.js application that owns its full vertical stack and is deployed as its own Knative Service.

LayerA zone owns…
UIIts own Next.js app + routes. Zones are stitched into one product in the browser via Next.js Multi-Zones under a single domain.
LogicIts own business logic. No shared backend runtime; no synchronous request-time calls into another zone's internals.
DataIts own PostgreSQL database (provisioned via CloudNativePG). No shared schema, no shared database.
LifecycleIts own build, deploy, rollback, and scale-to-zero — owned by one team, released on its own cadence.

Because each zone is a normal Next.js app, it is deployed by knext exactly like any other app — one NextApp custom resource → one Knative Service that scales independently to zero.

Data sovereignty (the hard rule)

This is the constraint that makes the systems self-contained:

  • A zone owns its data store; there is no shared database.
  • A zone must not read another zone's database — never connect to another zone's primary (-rw) or replica (-ro) service.
  • Cross-zone data flows only via async Kafka domain events (each zone keeps its own projection/copy) and via the browser (links / UI composition). Transient cross-tab UI state may use BroadcastChannel.
  • A zone reaches its own DB via DATABASE_URL from a Kubernetes Secret — never a hardcoded host.

How zones integrate

Integration is deliberately loose — never a distributed monolith:

  • In the browser (synchronous, UI-level). Next.js Multi-Zones composes the independent zone apps under one domain; navigation between zones is just links. The user sees one product; each zone ships and scales on its own.
  • Via domain events (asynchronous, data-level). When one zone's state changes in a way another zone cares about, it publishes a Kafka domain event. Subscribers keep their own copy. No zone ever reaches into another's database to read "live" state.

What's in knext core today vs recipe-level

CapabilityStatus
Knative / scale-to-zero, the official adapter, per-zone deployIn core.
assetPrefix wiring (when the zone's object storage is configured)In core.
Serving the App Shell, generating the precache manifestRecipe-level — an app-level pattern today, not generated by knext core.
Service Worker / Server-Worker-Interface / BroadcastChannel / Module-Federation runtimeRecipe-level — app template, not core yet.
generate zone scaffolder, micro-frontend (asset + runtime) isolation, PWA flagEnd goal — design + app-level today; promoted into the framework after the adapter-correctness work lands.

Composing zones behind one origin

knext deploys each zone as its own NextApp, but stitching several zones into one product behind a single public domain follows Next.js's own Multi-Zones guide: one zone's app acts as the public entry point and uses rewrites() to forward path prefixes to the other zones, each of which sets an assetPrefix so their static assets don't collide.

Static-asset collision without storage. knext only sets a zone's assetPrefix automatically when that zone's storage is configured — in that mode assets are served from an absolute, per-zone-namespaced URL and can't collide. Deploy a zone without storage configured and knext sets no assetPrefix at all, so the build serves /_next/static/... from the image at the origin root. Put two such zones behind the same public origin and their _next/static paths collide. Fix it one of two ways: configure storage on each zone (recommended — the absolute, collision-proof prefix comes for free), or set a per-zone assetPrefix (with a matching basePath) by hand in that zone's next.config.ts, per the Next.js Multi-Zones guide.

Two more requirements from the Next.js Multi-Zones guide apply directly to knext zones composed this way:

  • Server Actions need experimental.serverActions.allowedOrigins. Because the single public domain now serves several independent Next.js apps, each zone that accepts Server Action requests must list that shared origin in allowedOrigins, or Next.js rejects the action as cross-origin.
  • Cross-zone links must use a plain <a>, not <Link>. <Link> prefetches and soft-navigates within its own app's route tree, so a <Link> pointed at another zone's path won't work. Navigation between zones is always a full page load — use a plain <a href="..."> for any link that crosses a zone boundary.

Caching & security

Service-worker / caching config must never cache auth endpoints or any mutation route (network-only). Caching them is a correctness and security bug. This mirrors knext's platform-wide rule that there are no unauthenticated mutating endpoints.

Sequencing. Zone generation, micro-frontend isolation, and the PWA stitching layer stay design + app-level template for now, then get promoted into the framework as the core correctness work matures. The adapter itself is now validated against the official Next.js compatibility suite; the comprehensive SCS framework is the end-state built on top of that verified foundation.

On this page