Skip to content

0002 - Next.js App Router for the tenant console

  • Status: Accepted
  • Date: 2026-07-03
  • Deciders: nexus core team
  • Tags: frontend, console, nextjs, bff, auth

The tenant console is the primary human interface to nexus: dashboards over request analytics, model registry management, provider-key administration, audit browsing, and artifact upload/download. It authenticates against the auth service with a session cookie and reads from the control-plane, query, and audit services. Access tokens for those backends must never reach the browser; the console needs a server-side layer that holds the session and attaches credentials on the tenant’s behalf. Storage flows additionally require server-side proxying of multipart uploads so artifact writes carry the same authenticated identity.

  • HTTP-only session-cookie auth with server-side token exchange; no bearer tokens in browser JavaScript.
  • A single enforcement point for backend calls (control-plane, query, audit, artifacts) rather than per-page fetch logic.
  • Velocity for a data-heavy UI: tables, charts, filters, live views over ClickHouse-backed query endpoints.
  • One TypeScript toolchain shared with the rest of the pnpm workspace.
  • Next.js (App Router) with BFF API routes — chosen.
  • SPA + separate API client (React/Vite talking directly to backends) — rejected: the browser would have to hold and refresh backend tokens itself, which violates the constraint that credentials stay server-side; CORS and per-service auth handling multiply the attack surface.
  • Server-rendered Rust templating (askama / maud behind axum) — rejected: keeps everything in one language, but the component ecosystem and iteration speed for a data-heavy interactive UI (tables, charts, streaming views) are far behind the React ecosystem.
  • Other meta-frameworks (Remix, SvelteKit) — viable BFF stories, but smaller ecosystems for the component libraries the console leans on, and no team familiarity advantage over Next.js.

Chosen option: Next.js with the App Router, deployed as its own service.

  • The console lives at apps/console/ with App Router routes under app/, using route groups such as (console) and (auth) to separate authenticated and unauthenticated layouts.
  • All backend access goes through BFF API routes under apps/console/app/api/: session sign-in/sign-up/refresh/sign-out routes set and read the HTTP-only session cookie, and proxy routes (for example control-plane-proxy.ts, query, audit, artifacts/[...path]) attach access-token headers server-side before forwarding.
  • Artifact upload/download is proxied through the artifacts API routes, including multipart bodies, so object storage is never exposed to the browser directly.
  • Build configuration is apps/console/next.config.mjs; the service ships as a Node container built from apps/console/Dockerfile.
  • The image set gains a Node runtime alongside the Rust services; the console is the only non-Rust service binary.
  • The BFF layer is the enforcement point: every browser-originated backend call passes through an API route that validates the session cookie, so token handling is auditable in one place.
  • The console participates in the pnpm workspace and consumes shared @nexus/* packages, keeping generated API types and UI utilities in one dependency graph.
  • Two toolchains (Cargo and pnpm) must stay healthy in CI and local dev.