Lands the minimum surface so a developer can: cd platform/orca-platform && make dev-up cd platform/tenant-registry && make dev cd platform/portal && make install && make dev open http://acme.localhost:3000 and complete a real OIDC sign-in against the breakpilot-dev realm. Layout: src/middleware.ts host→slug URL rewrite; backstage carve-out src/auth.ts Auth.js v5 Keycloak provider; passes tenant_id/slug/org_roles/products/plan/status claims through to the session src/app/api/auth/[...nextauth]/ Auth.js handlers (GET, POST) src/app/layout.tsx root html shell src/app/page.tsx apex landing src/app/[slug]/layout.tsx fetches tenant via lib/tenant-registry src/app/[slug]/page.tsx redirect to /dashboard src/app/[slug]/dashboard/page.tsx signed-out → Sign in with Keycloak signed-in → welcome + Sign out src/lib/host.ts testable host parser (apex/tenant/backstage) src/lib/tenant-registry.ts fetch client for the Go service Tooling: vitest 13 tests, 100% coverage of src/lib/ Next.js 15 build compiles all routes; output: standalone ESLint flat config next/core-web-vitals + next/typescript Real RBAC enforcement, the rest of the customer-area surfaces, and the backstage shell land per the M5.2 / M10.1 schedule. This is just enough to be the first thing a developer codes in. Refs: M5.1 (skeleton)
portal
Next.js 15 customer area + backstage.
Part of the Breakpilot Platform. For the big picture see
platform/docs: Architecture · Infrastructure · Product Integration Spec · Implementation Plan
What this is
Next.js 15 customer area + backstage. Scaffolded under milestone M5.1. See platform/docs for the full architecture context.
Plane: Control Owner: @sharang Status: pre-alpha Linked milestone: M5.1
Run locally
# Prerequisites: Node 20+, pnpm 9+, the dev stack running.
# 1. Bring up Keycloak + Postgres + Redis (separate clone):
cd /path/to/platform/orca-platform && make dev-up
# 2. Run tenant-registry (separate clone):
cd /path/to/platform/tenant-registry && make dev
# 3. Run this app:
make install # pnpm install --frozen-lockfile
make dev # next dev on http://localhost:3000
# Or hit a real tenant immediately:
# open http://acme.localhost:3000 → redirects to Keycloak → back to /acme/dashboard
Seed login (from the dev-stack realm): test@breakpilot.dev / test.
make test / make lint / make typecheck / make build run vitest / next lint / tsc / next build respectively.
Env vars live in .env.example. Copy to .env.local for local overrides (gitignored).
Surface
| Route | Renders |
|---|---|
http://localhost:3000/ |
Apex landing — pointer to tenant subdomains |
http://<slug>.localhost:3000/ |
Middleware rewrites to /[slug]/ → redirects to /[slug]/dashboard |
http://<slug>.localhost:3000/dashboard |
OIDC-gated dashboard; signed-out users see "Sign in with Keycloak" |
http://backstage.localhost:3000/ |
(Skeleton) backstage route — rewritten to /__backstage__/* |
/api/auth/[...nextauth] |
Auth.js v5 endpoints (callback, signin, signout, jwt) |
Architecture notes
- Host → slug routing:
src/middleware.tsparsesHostheader viaparseHost()(insrc/lib/host.ts) and rewrites the request path to/<slug>/.... URL bar stays unchanged. Apex hosts and unknown subdomains fall through unmodified. - Tenant context:
src/app/[slug]/layout.tsxfetches the tenant fromtenant-registry(src/lib/tenant-registry.ts). 404 →notFound(); HTTP errors bubble up. - Auth:
src/auth.tsis the Auth.js v5 config — Keycloak provider, tenant-context claims (tenant_id,tenant_slug,org_roles,products,plan,tenant_status) propagated via JWT/session callbacks. Real RBAC enforcement lands in M5.2 / M10.1.
Deployment
| Env | URL | How |
|---|---|---|
| dev | http://localhost:3000 |
make dev |
| stage | https://portal.stage.breakpilot.com |
auto on merge to main |
| prod | https://portal.breakpilot.com |
manual: tag vX.Y.Z + sign-off |
Rollback: orca rollout undo portal --env={{env}}.
Observability
- Traces, logs, metrics: SigNoz — service name
portal - Audit events: Tenant Registry
/audit(Retraced-shape schema) - On-call:
oncall@breakpilot.com· runbook atplatform/docs/runbooks/portal.md
Contributing
See CONTRIBUTING.md. TL;DR: branch from main, open a PR, 1 review + green CI, squash-merge.
License
Proprietary — all rights reserved. Copyright (c) 2026 Sharang Parnerkar and Benjamin Boenisch. See LICENSE.