# 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.
