Trovella Wiki

ADR-005: Framework -- Next.js 16 + tRPC v11

Decision record for choosing Next.js 16 with App Router, tRPC v11 for internal APIs, and the five-library state management stack.

Status: Accepted Date: 2026-03-21 (Week 0 Decision Sprint), modified 2026-03-25 (TRO-6/TRO-7 scaffolding) Deciders: Kyle Olson (Solo Founder)

Decision

  • Framework: Next.js 16.2 with App Router, React 19, Turbopack
  • Language: TypeScript 5.9 strict (noUncheckedIndexedAccess, exactOptionalPropertyTypes)
  • Internal API: tRPC v11 -- end-to-end type safety with zero code generation
  • Public API: REST via trpc-openapi bridge (deferred to post-traction)
  • State stack: Zustand (client), TanStack Query v5 (server/cache), nuqs (URL), React Hook Form (forms), Zod v4 (validation) -- ~32KB total gzipped
  • UI: shadcn/ui + Tailwind CSS v4 + Lucide icons
  • Output: Standalone mode for Docker containerization

Context

Solo founder building a multi-tenant SaaS with AI coding agents doing the majority of implementation. The framework stack must:

  • Optimize for AI agent code generation accuracy -- agents need large training data corpora and predictable patterns
  • Provide end-to-end type safety so the TypeScript compiler catches errors before deployment
  • Support Server Components as the default rendering mode (simpler mental model, less client JS)
  • Run on any platform (no Vercel lock-in) -- currently deployed on a Compute Engine VM
  • Support future React Native/Expo mobile app (Phase 2)

Alternatives Considered

Server Actions Only (no tRPC)

Server Actions lack a built-in middleware chain -- every 'use server' function creates a public HTTP endpoint that bypasses middleware, type guards, and component-level protections. Auth, RLS, and CASL checks would need to be manually composed per action, effectively rebuilding tRPC's authorizedProcedure pattern without the structural guarantees. Server Actions also cannot be called from React Native, blocking Phase 2 mobile reuse. Production experience from other SaaS teams (notably Documenso, which migrated to Server Actions and reverted to tRPC within a day) further validated tRPC as the safer choice.

GraphQL (Apollo)

Apollo Studio costs $59-399/month. Schema + resolver + dataloader complexity is error-prone for AI agents. 1864ms vs 922ms REST latency in benchmarks. Requires 3-4 person team to maintain properly. Rejected for solo-dev overhead.

Plain REST

30-40% slower development due to type duplication boilerplate (25-40 lines per endpoint vs tRPC's 5-10). Used only for webhooks and future public API via trpc-openapi bridge.

oRPC v1.0

Released late 2025 -- insufficient AI training data, ecosystem unproven. Deferred evaluation to 2027.

Implementation Notes Relevant to Routing

output: "standalone" for Docker

Set during TRO-7 (CI/CD) for Docker containerization. Produces ~150MB images. Requires pg, pino, pino-pretty in serverExternalPackages. See Docker Containers.

Next.js Pinned to Exact Version

Firebase App Hosting's version checker reads package.json literally, not through pnpm's catalog resolution. Both catalog: and ^16.2.1 failed. Fix: pin "16.2.1" exactly in apps/web/package.json, bypassing the pnpm catalog.

.env Lives in apps/web/

Next.js loads .env from its own project directory, not the monorepo root. Running pnpm dev from root via Turbo silently ignores the root .env. Convention: apps/web/.env (gitignored), with @repo/db scripts using explicit tsx --env-file-if-exists=../../.env.

Extensionless Imports

Turbopack cannot resolve .js-suffixed imports from .ts source files in non-built workspace packages. All relative imports across packages use extensionless paths.

Consequences

Positive

  • End-to-end type safety from @repo/db through @repo/api to apps/web at compile time, zero codegen
  • 30-40% faster API development -- tRPC procedures are 5-10 lines vs 25+ for REST
  • AI agent accuracy -- dominant ecosystem with massive training data
  • Platform-independent -- runs on any Node.js host with standalone output

Negative

  • TypeScript pinned to 5.9 (not 6.0) due to typescript-eslint ecosystem lag
  • shadcn/ui generated files override design tokens and fail lint; requires manual reconciliation
  • SSR hydration complexity with React 19 Server Components

Risks

  • tRPC language server performance may degrade at 100+ procedures (mitigated by domain-specific router splitting)
  • Next.js development heavily influenced by Vercel priorities (mitigated by standalone output, no Vercel-specific features)

Validation

RuleEnforcement
TypeScript strict mode across all packages@repo/typescript-config shared config
tRPC procedures use authorizedProcedureArchitecture test in CI
No direct SDK imports in apps/webESLint no-restricted-imports + dependency-cruiser
Standalone output for deploymentnext.config.ts sets output: "standalone"
Lint + format + typecheck on every PRCI quality job

References

  • Full ADR: docs/architecture/decisions/005-framework-nextjs-trpc.md
  • Architecture: Request Lifecycle
  • Related: ADR-004 (Authorization -- procedure chain), ADR-011 (Monorepo -- Turborepo + pnpm)
  • Linear: TRO-6, TRO-7, TRO-9, TRO-10, TRO-13

On this page