NAI OS
Twelve decisions for AI-assisted web app development, cheap now and expensive later.
This portfolio's three web apps (nai-analysis, nai-onto,
nai-prestolex) were built separately and converged anyway: same framework generation,
same database and auth provider, same hosting, in two cases the same palette to the hex digit. The
golden-path template captures those winners as a checklist walked before the first line of
code. Where the three apps agree, that answer is the default: take it unless the product
forces a different one. Where their divergence is motivated, the aspect is marked
decide per app and the template only says what the decision must cover.
Reuse happens at generation time, not run time. The marginal cost of regenerating boilerplate with Claude Code is close to zero for a solo maintainer, while a shared package costs what it always did: version coupling, breaking-change ceremony, forced rewrites of working apps. So the winning choices are encoded as conventions Claude Code applies when scaffolding a new app, and each app owns its copy from then on. The template is a snapshot, not a dependency: existing apps do not upgrade when it changes, and that drift is accepted as the price of decoupling. The public-facing argument is the blog post Reuse Moved to Generation Time.
Each one shapes the stack, the hosting, and the migrations you will (or will not) have to do later. Walk all twelve; skipping one is itself a decision, just an unexamined one.
Server-rendered, SPA, static, or hybrid: the choice shapes framework, hosting, SEO, and perceived performance. Default: Next.js 16 App Router + React 19, TypeScript strict, npm. Server-rendered by default; client components only where interactivity demands them.
Who logs in and how, session handling, and where identity lives.
Default mechanics: Supabase Auth with the standard @supabase/ssr
wiring; all three apps carry this boilerplate verbatim, so copy it, do not re-derive it.
Decide per app: the auth surface. Google-only fits a closed client
tool, email + password fits a multi-role workspace, account-free participation fits a consumer
game. Shared identity is deferred until real user overlap exists.
Roles, permissions, and above all tenancy; retrofitting multi-tenancy is one of the most painful migrations there is. The survey found three trust models and three correct answers: RLS as the only gate, app-layer RBAC mirroring RLS, and service-role single-writer with version-guarded optimistic concurrency. Non-negotiable: every app gets its own Supabase project; separate projects are the security architecture, not fragmentation.
Relational vs. document, migration management, where blobs go, and the ID strategy that leaks
into URLs and APIs. Default: Supabase Postgres, migrations under version control
from the first table, .env.example committed, .env.local gitignored, no
secret ever in the repo.
REST, GraphQL, or RPC-ish server actions, plus versioning and contract validation.
Default: Server Actions as the primary transport, returning
ActionResult { ok, errors[], values } with an ICU error-code catalog; the strongest
of the three surveyed patterns, and it transfers even where the transport differs.
Platform, environments, CI/CD, previews, rollback. Default: Vercel on the
existing team, with CI (typecheck, lint, tests) in front of every deploy: push-triggered deploy
with manual discipline was the gap all three apps shared. Naming hygiene from day one; repo,
Vercel project, and Supabase project all carry the final nai-* name.
A consistent error model, structured logging, monitoring, alerting; decided late, this ends
up as console.log archaeology. Default: error monitoring wired up
before first deploy. User-facing errors go through the ActionResult
error-code catalog (aspect 5); internal errors go to the monitoring tool.
Input validation, CSRF/XSS posture, secrets, CORS, rate limiting, dependency updating: cheapest as a standing convention, dearest as a retrofit. Default: RLS on every table, service-role keys server-side only, validation at the Server Action boundary. Confidential client data never shares a blast radius with consumer surfaces (aspect 3).
What is source of truth where; cache invalidation decisions ripple through the whole UX. Default: no client state library, the server is the source of truth. All three apps arrived at this stance independently, the best evidence it is right for this portfolio. Client-side caches only where a product need (realtime play) demands them.
Component library, tokens, theming, breakpoints, accessibility target: consistency here is nearly impossible to bolt on. Default for professional tools: Tailwind v4 + shadcn/ui, themed by the shared Nink AI token sheet, vendored by copy. Decide per app: consumer brands are exempt, and the UI concept itself follows the product.
Date, time, number, currency, time zones, and room for locales later; time zones especially punish late deciders. Default: next-intl with DE/EN from the start, a standing requirement of this portfolio already solved three different ways. Store timestamps in UTC; format in the user's locale at the edge.
GDPR posture (consent, data residency, deletion path), imprint and privacy page, cookie policy, retention rules. For a German GmbH shipping to EU users this aspect is load-bearing, and it interacts with aspect 3: per-app Supabase projects keep deletion and residency answers per-product instead of entangled.
The template stops where the three apps stop. These aspects are real and will earn a walk-through of their own the first product that needs them; for now they are named, not answered.
What this page is not: a runtime dependency, and not the source of truth. The
living convention document is docs/web-app-golden-path.md in the repo, adopted by the
web app platformization decision of 2026-08-20; this page is its architecture-tour summary. When
the two disagree, the markdown wins.