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
Context and Problem Statement
Section titled “Context and Problem Statement”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.
Decision Drivers
Section titled “Decision Drivers”- 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.
Considered Options
Section titled “Considered Options”- 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.
Decision Outcome
Section titled “Decision Outcome”Chosen option: Next.js with the App Router, deployed as its own service.
- The console lives at
apps/console/with App Router routes underapp/, 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 examplecontrol-plane-proxy.ts,query,audit,artifacts/[...path]) attach access-token headers server-side before forwarding. - Artifact upload/download is proxied through the
artifactsAPI 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 fromapps/console/Dockerfile.
Consequences
Section titled “Consequences”- 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.
