# Port Nimara CRM — Full-Stack Architecture Comparison **Compiled:** 2026-03-11 **Context:** AI-first development (Claude Code + Codex), self-hosted Docker, real-time updates required, PostgreSQL + Drizzle ORM already decided, website is separate codebase needing public API --- ## Constraints (apply to ALL options) These are fixed regardless of framework choice: | Constraint | Detail | | ------------ | -------------------------------------------------------------------------------- | | Database | PostgreSQL + Drizzle ORM | | Auth | Keycloak OIDC (existing infrastructure) | | File storage | MinIO S3 (existing infrastructure) | | E-signatures | Documenso (existing infrastructure, self-hosted) | | Deployment | Docker containers, self-hosted server, Gitea CI/CD | | Real-time | Live updates required (berth status changes, interest updates, signature events) | | Public API | REST endpoints for website berth map + interest registration | | Development | AI-assisted (Claude Code / Codex writing ~90%+ of code) | | Team size | Solo developer (Matt) + AI tools | --- ## Option A: Next.js (React) + tRPC ### The Stack | Layer | Technology | | ---------------- | ------------------------------------------------------------ | | Framework | Next.js 15 (App Router) | | Language | TypeScript (strict) | | UI library | React 19 | | Styling | Tailwind CSS + Maritime design tokens | | Components | shadcn/ui (copy-paste, Radix primitives, fully customizable) | | Internal API | tRPC v11 (type-safe, no API routes to define for CRM pages) | | Public API | Next.js Route Handlers (REST endpoints for website) | | Real-time | tRPC subscriptions over WebSocket (via `ws` or `uWebSocket`) | | State | TanStack Query (server state) + Zustand (UI state) | | ORM | Drizzle ORM | | Jobs | BullMQ + Redis | | Containerization | Docker (node:20-alpine) + nginx reverse proxy | ### How it handles your CRM use cases **Interest CRUD**: tRPC procedure `interests.create` / `interests.update` / `interests.list` — fully type-safe from database schema to UI component. Change a field in Drizzle schema, TypeScript immediately flags every component that references it. **Real-time berth status**: tRPC subscription `berths.onStatusChange` — when a berth is linked to an interest and auto-moves to "Under Offer", all connected clients receive the update via WebSocket. No polling needed. **EOI/Documenso workflow**: Server-side tRPC procedures handle the full lifecycle. Webhook endpoint is a standard Next.js Route Handler (REST, since Documenso calls it externally). **Public berth map API**: Standard Next.js Route Handlers — `GET /api/public/berths` and `POST /api/public/interests`. These are separate from tRPC and serve the website. **Spec sheet import (AI-assisted)**: Upload endpoint receives PDF/Excel, server-side processing with SheetJS (Excel) or PDF extraction, AI API call to interpret/map columns, preview diff shown to admin, confirmation triggers bulk upsert via Drizzle. **Docker deployment**: Official self-hosting guide. Standalone output mode produces a minimal Node.js server. Requires nginx reverse proxy for production (handles TLS, rate limiting, slow connections). Redis sidecar for BullMQ + session cache. ### Assessment | Dimension | Rating | Notes | | ----------------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | AI code generation quality | **Excellent** | Largest training corpus. Claude Code is most fluent with React/Next.js. shadcn/ui has massive example coverage. | | Component ecosystem | **Excellent** | Deepest of any framework. Every library has a React-first version. TanStack Table, React Hook Form, Recharts, etc. | | tRPC integration | **Excellent** | First-class support. Most tRPC examples are Next.js. | | Real-time (WebSocket) | **Good** | Works but requires separate WebSocket server alongside Next.js (Vercel doesn't support WS, but irrelevant for self-hosting). Needs `next-ws` or custom server. | | Self-hosted Docker | **Good** | Works well with standalone output mode. Requires nginx reverse proxy. More configuration than Nuxt/SvelteKit. | | Bundle size / performance | **Adequate** | React runtime is larger than alternatives. For an internal CRM used by a small team, this is irrelevant. | | Learning curve (reading code) | **Moderate** | JSX, hooks, useEffect patterns. React has more "gotchas" than Vue/Svelte but AI handles them well. | | Long-term maintenance | **Excellent** | Backed by Vercel. Massive community. Won't disappear. | **Pros:** - AI tools generate the most reliable code in this ecosystem - Largest component library ecosystem by far - tRPC + Drizzle + Next.js is an extremely well-documented combination - shadcn/ui gives you beautiful, accessible components you fully own and can restyle to Maritime - TanStack Query handles caching, optimistic updates, background refresh out of the box - Most Stack Overflow answers, most GitHub examples, most blog posts **Cons:** - React's mental model (hooks, closures, re-renders) can be confusing when reading/debugging - WebSocket support needs custom server setup (not complex, but not built-in) - Next.js is optimized for Vercel — self-hosting works but you miss some platform features - Heavier runtime than Svelte (irrelevant for small team internal tool) - App Router patterns still evolving — some tutorials use old Pages Router --- ## Option B: Nuxt 3 (Vue) + tRPC ### The Stack | Layer | Technology | | ---------------- | ------------------------------------------------------------------- | | Framework | Nuxt 3 (latest) | | Language | TypeScript (strict) | | UI library | Vue 3 (Composition API) | | Styling | Tailwind CSS + Maritime design tokens | | Components | Nuxt UI v3 (Radix Vue based, Tailwind-native) OR Radix Vue + custom | | Internal API | trpc-nuxt (community adapter) | | Public API | Nitro server routes (REST endpoints for website) | | Real-time | Nitro WebSocket (experimental) or Socket.io alongside Nitro | | State | VueQuery / TanStack Query Vue (server state) + Pinia (UI state) | | ORM | Drizzle ORM | | Jobs | BullMQ + Redis | | Containerization | Docker (node:20-alpine), Nitro handles both API + SPA | ### How it handles your CRM use cases **Interest CRUD**: trpc-nuxt procedures with same type-safety pattern as Next.js. Vue's Composition API (`ref`, `computed`, `watch`) is arguably more intuitive than React hooks for someone reading code. **Real-time berth status**: Nitro's experimental WebSocket support (CrossWasm v1 in Nitro v3). Less mature than the Next.js WebSocket ecosystem. Alternatively, run Socket.io alongside Nitro — works but adds complexity. **EOI/Documenso workflow**: Nitro server routes handle webhooks. Same pattern as current system (Nitro is what you already have). **Public berth map API**: Nitro server routes — cleaner than Next.js Route Handlers. Nitro is genuinely good at this. **Spec sheet import**: Same approach as Next.js — server-side processing with AI interpretation. **Docker deployment**: Nitro's output is a self-contained Node.js server. No reverse proxy strictly required (though recommended). Simpler Docker setup than Next.js. ### Assessment | Dimension | Rating | Notes | | ----------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------- | | AI code generation quality | **Good** | Well-represented in training data. Slightly fewer examples than React. Claude Code handles Vue/Nuxt competently. | | Component ecosystem | **Good** | Nuxt UI v3 is excellent and built specifically for Nuxt. Fewer third-party options than React. | | tRPC integration | **Adequate** | Community adapter (trpc-nuxt). Works but fewer examples, less battle-tested than Next.js tRPC. | | Real-time (WebSocket) | **Adequate** | Nitro WebSocket is experimental. Works with adapter-node but less mature. Socket.io fallback is proven. | | Self-hosted Docker | **Excellent** | Nitro produces clean standalone output. Simplest Docker setup of the three. | | Bundle size / performance | **Good** | Smaller than React, larger than Svelte. | | Learning curve (reading code) | **Easy** | Vue's template syntax is the most readable. `