Files
pn-new-crm/04-ARCHITECTURE-COMPARISON.md

317 lines
23 KiB
Markdown
Raw Permalink Normal View History

# 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. `<script setup>` is clean. Closest to plain HTML. |
| Long-term maintenance | **Good** | Backed by Nuxt Labs. Strong community. Stable but smaller than React ecosystem. |
**Pros:**
- You already have familiarity with the patterns from the current codebase
- Vue's template syntax is the most human-readable (easiest to understand when reviewing AI output)
- Nitro server is genuinely excellent for API routes and deployment flexibility
- Nuxt UI v3 is tightly integrated and well-maintained by the Nuxt team
- Simplest Docker deployment (Nitro just works)
- Pinia + VueQuery is a clean state management story
**Cons:**
- Smaller AI training corpus than React — more edge cases where Claude Code may hallucinate APIs
- tRPC support is community-maintained, not official
- WebSocket/real-time support is less mature than alternatives
- Fewer third-party component libraries to choose from
- Risk of repeating patterns from the current codebase rather than improving them
---
## Option C: SvelteKit + tRPC
### The Stack
| Layer | Technology |
| ---------------- | --------------------------------------------------------------- |
| Framework | SvelteKit 2 |
| Language | TypeScript (strict) |
| UI library | Svelte 5 (runes) |
| Styling | Tailwind CSS + Maritime design tokens |
| Components | Skeleton UI or shadcn-svelte (Bits UI based) |
| Internal API | trpc-sveltekit (community adapter) |
| Public API | SvelteKit server routes (REST endpoints for website) |
| Real-time | trpc-sveltekit experimental WS or Socket.io sidecar |
| State | TanStack Query Svelte (server state) + Svelte stores (UI state) |
| ORM | Drizzle ORM |
| Jobs | BullMQ + Redis |
| Containerization | Docker (node:20-alpine) via adapter-node |
### How it handles your CRM use cases
**Interest CRUD**: trpc-sveltekit procedures. Svelte's reactivity model is the simplest of the three — `$state` rune replaces React's `useState`, Vue's `ref`. Less boilerplate.
**Real-time berth status**: Experimental WebSocket via trpc-sveltekit (only works with adapter-node). Less mature than both Next.js and Nuxt options.
**EOI/Documenso workflow**: SvelteKit server routes. Clean pattern but fewer examples specific to webhook handling.
**Public berth map API**: SvelteKit server routes. Works well, similar to Nuxt's Nitro routes.
**Spec sheet import**: Same approach as others. Fewer AI-processing library examples in Svelte ecosystem.
**Docker deployment**: adapter-node produces a clean, lightweight Node server. Smallest footprint of the three.
### Assessment
| Dimension | Rating | Notes |
| ----------------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------- |
| AI code generation quality | **Adequate** | Smallest training corpus. Svelte 5 runes are new — AI tools may generate Svelte 4 patterns. More manual correction needed. |
| Component ecosystem | **Limited** | Skeleton UI and shadcn-svelte exist but are less mature. Many libraries are React/Vue only. |
| tRPC integration | **Adequate** | Community adapter. WebSocket support explicitly marked "experimental". |
| Real-time (WebSocket) | **Limited** | SvelteKit doesn't natively support WebSockets. Experimental via adapter-node only. Most constrained option. |
| Self-hosted Docker | **Excellent** | Smallest container image. Lowest resource usage. Cleanest adapter-node output. |
| Bundle size / performance | **Excellent** | 40-60% smaller bundles than React. Fastest runtime. Lowest memory usage. |
| Learning curve (reading code) | **Easy** | Most intuitive syntax. Closest to vanilla HTML/JS. Svelte 5 runes are clean. |
| Long-term maintenance | **Good** | Strong community, Vercel backing. Smaller ecosystem means fewer resources if you hit edge cases. |
**Pros:**
- Smallest bundle size and lowest resource usage (best for self-hosting efficiency)
- Most intuitive syntax — easiest to read and understand as a non-programmer
- Svelte 5 runes are the cleanest reactivity model available
- Lightest Docker footprint
- Developer experience is genuinely the best of the three for humans
**Cons:**
- Smallest AI training corpus — Claude Code and Codex will produce less reliable code, especially for complex patterns
- Svelte 5 is recent — AI tools may mix up Svelte 4 and Svelte 5 syntax
- Weakest real-time/WebSocket story — experimental only
- Smallest component ecosystem — you'll build more from scratch
- Fewer Stack Overflow answers and GitHub examples to draw from when stuck
- Some React-only libraries have no Svelte equivalent (form builders, advanced table components, etc.)
---
## Option D: Next.js (React) + REST (no tRPC)
Same as Option A but replacing tRPC with traditional REST API routes. Included because tRPC adds complexity and you may prefer a simpler architecture.
### Differences from Option A
| Aspect | tRPC (Option A) | REST (Option D) |
| ---------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------- |
| API definition | Procedures on server, auto-typed on client | Route Handlers on server, manual fetch + types on client |
| Type safety | Automatic end-to-end | Manual (Zod schemas shared, but not enforced across boundary) |
| Code generation | Less code (no fetch wrappers needed) | More code (API client layer, type definitions) |
| Public API for website | Separate REST routes alongside tRPC | Same REST routes serve both CRM and website |
| Real-time | tRPC subscriptions (WebSocket) | Socket.io or Server-Sent Events (separate from API) |
| External consumers | tRPC only works with TypeScript clients | REST works with any client (mobile app, website, third-party) |
| Complexity | Higher (tRPC + REST coexist for different consumers) | Lower (one pattern for everything) |
| Debugging | tRPC calls don't show in browser Network tab as clean REST calls | Standard HTTP requests, easy to inspect |
### Assessment
| Dimension | Rating | Notes |
| -------------------------- | ------------- | ---------------------------------------------------------------------------------------- |
| AI code generation quality | **Excellent** | Even more training data for REST patterns than tRPC. |
| Simplicity | **Excellent** | One pattern. No tRPC learning curve. Every endpoint is a standard HTTP route. |
| Type safety | **Good** | Zod validation + shared types. Not automatic like tRPC but works well. |
| Real-time | **Good** | Socket.io alongside Next.js is well-documented. Or Server-Sent Events for simpler cases. |
| External API consumers | **Excellent** | Same API serves CRM, website, and any future mobile app or integration. |
| OpenAPI compatibility | **Excellent** | REST routes can auto-generate OpenAPI docs. tRPC cannot. |
**Pros:**
- Simplest mental model — everything is HTTP requests
- One API pattern instead of tRPC + REST coexisting
- Easier to debug (browser Network tab shows clean requests)
- Same API serves CRM and website (no separate public routes needed)
- If you ever build a mobile app or external integration, the API is ready
- More AI training examples for REST than tRPC
**Cons:**
- More boilerplate (API client layer, manual type sharing)
- No automatic type safety across client-server boundary
- Need to manually keep request/response types in sync
- Real-time requires a separate Socket.io or SSE layer
---
## Head-to-Head Comparison
| Factor | Next.js + tRPC (A) | Nuxt 3 + tRPC (B) | SvelteKit + tRPC (C) | Next.js + REST (D) |
| ----------------------------- | ------------------ | ----------------- | -------------------- | ------------------ |
| **AI code gen reliability** | ★★★★★ | ★★★★ | ★★★ | ★★★★★ |
| **Component ecosystem** | ★★★★★ | ★★★★ | ★★★ | ★★★★★ |
| **Real-time maturity** | ★★★★ | ★★★ | ★★ | ★★★★ |
| **Docker self-hosting** | ★★★★ | ★★★★★ | ★★★★★ | ★★★★ |
| **Type safety (end-to-end)** | ★★★★★ | ★★★★ | ★★★★ | ★★★ |
| **Code readability** | ★★★ | ★★★★ | ★★★★★ | ★★★ |
| **Architecture simplicity** | ★★★ | ★★★ | ★★★ | ★★★★★ |
| **Bundle size / performance** | ★★★ | ★★★★ | ★★★★★ | ★★★ |
| **Public API flexibility** | ★★★ | ★★★★ | ★★★ | ★★★★★ |
| **Long-term ecosystem** | ★★★★★ | ★★★★ | ★★★★ | ★★★★★ |
---
## Recommendation Matrix
**If AI code generation reliability is your top priority** → Option A or D (Next.js)
**If deployment simplicity matters most** → Option B (Nuxt) or C (SvelteKit)
**If you want the simplest architecture to understand and maintain** → Option D (Next.js + REST)
**If you want maximum type safety with minimum code** → Option A (Next.js + tRPC)
**If performance and resource efficiency matter most** → Option C (SvelteKit)
---
## Author's Recommendation
**Option D (Next.js + REST)** is the strongest fit for your specific situation. Here's why:
1. **AI-first development**: Next.js React has the deepest training corpus. REST has even more examples than tRPC. You're optimizing for AI output quality, and this combination maximizes it.
2. **Architectural simplicity**: tRPC is elegant but adds a second API paradigm alongside the REST routes you need for the website anyway. With REST-only, every endpoint follows the same pattern — simpler for AI tools to generate consistently and simpler for you to understand.
3. **Real-time**: Socket.io alongside Next.js is battle-tested and well-documented. Simpler than tRPC subscriptions, which require a custom WebSocket server.
4. **Public API**: The same REST endpoints serve the CRM frontend, the website berth map, and any future integration. No separate public routes to maintain.
5. **Future-proofing**: If you ever need a mobile app, an external integration, or OpenAPI documentation, REST is ready. tRPC locks you into TypeScript clients.
6. **Self-hosting**: Next.js standalone output in Docker behind nginx is well-documented. Not the simplest (Nuxt wins there), but the trade-off for the ecosystem advantages is worth it.
The one thing you'd give up vs. Option A is automatic end-to-end type safety. But with Zod schemas shared between server and client plus TanStack Query's typed hooks, the gap is small in practice.
**Runner-up**: Option A (Next.js + tRPC) if you value automatic type safety highly and don't mind two API paradigms coexisting.