Files
pn-new-crm/04-ARCHITECTURE-COMPARISON.md
Matt 67d7e6e3d5
Some checks failed
Build & Push Docker Images / build-and-push (push) Has been cancelled
Build & Push Docker Images / deploy (push) Has been cancelled
Build & Push Docker Images / lint (push) Has been cancelled
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00

23 KiB

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.