Skip to content

← Back to README

K. Recommended Target Architecture


Target: Monorepo with npm Workspaces + Turborepo

Tooling: npm workspaces + Turborepo Node version: 24 LTS (unified; Active LTS as of May 2026) Language: TypeScript everywhere (reverb-backend migration phased) Package manager: npm (already used across all repos — no migration required)


Proposed Repository Structure

soreto-platform/
│
├── apps/
│   ├── platform-api/          ← reverb-backend (migrated, renamed)
│   │   ├── src/
│   │   │   ├── routes/api/
│   │   │   ├── services/
│   │   │   ├── middleware/
│   │   │   ├── config/
│   │   │   └── common/
│   │   ├── migrations/        ← stays here (reverb schema)
│   │   ├── test/
│   │   ├── Procfile           ← web: node dist/server.js
│   │   ├── package.json
│   │   ├── tsconfig.json      ← extends ../../tooling/tsconfig/base.json
│   │   ├── .env.example
│   │   └── CLAUDE.md
│   │
│   ├── platform-ui/           ← soreto-melissa (migrated)
│   │   ├── app/               ← Next.js 16 App Router (no src/)
│   │   │   ├── (admin)/       ← admin routes — guarded layout
│   │   │   ├── (client)/      ← client routes — CLIENT_USER, SAAS
│   │   │   ├── (full-page)/   ← auth, subscribe, public
│   │   │   ├── (landing)/     ← marketing
│   │   │   ├── layout.tsx     ← root layout
│   │   │   └── page.tsx
│   │   ├── components/        ← shadcn/ui components + Tailwind CSS v4
│   │   │   └── ui/            ← added via npx shadcn@latest add <component>
│   │   ├── service/           ← typed API calls via @soreto/api-client
│   │   ├── shared/            ← constants, helpers
│   │   ├── store/             ← React Context / Zustand
│   │   ├── types/             ← local TypeScript definitions
│   │   ├── public/            ← static assets
│   │   ├── globals.css        ← Tailwind v4 @theme directive (no tailwind.config.js)
│   │   ├── Procfile           ← web: npm start
│   │   ├── package.json
│   │   ├── tsconfig.json      ← extends ../../tooling/tsconfig/nextjs.json
│   │   ├── next.config.ts
│   │   ├── components.json    ← shadcn/ui CLI config
│   │   ├── .env.example
│   │   └── CLAUDE.md
│   │
│   ├── zoe/                   ← soreto-zoe (migrated, refactored)
│   │   ├── src/
│   │   │   ├── modules/
│   │   │   ├── services/
│   │   │   └── database/
│   │   ├── processors/        ← eventually typed
│   │   ├── migrations/        ← stays here (zoe schema)
│   │   ├── Procfile           ← worker: node dist/worker.js
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   ├── config.js          ← replace with @soreto/config
│   │   └── CLAUDE.md
│   │
│   └── legacy-portal/         ← reverb-react (modernized → Next.js 16 + React 19 + Node 24)
│       ├── app/               ← Next.js 16 App Router (no src/)
│       │   ├── (admin)/       ← admin routes (ported from reverb-react admin components)
│       │   │   ├── dashboard/
│       │   │   ├── campaigns/
│       │   │   ├── users/
│       │   │   ├── reports/
│       │   │   └── layout.tsx
│       │   ├── (client)/      ← client routes (ported from reverb-react client components)
│       │   │   ├── dashboard/
│       │   │   ├── campaigns/
│       │   │   └── layout.tsx
│       │   ├── layout.tsx     ← root layout (auth guard)
│       │   └── page.tsx       ← root redirect
│       ├── components/        ← shadcn/ui components + Tailwind CSS v4
│       ├── store/             ← Zustand stores (replaces Redux 3 + Flux)
│       ├── service/           ← typed API calls via @soreto/api-client
│       ├── types/             ← TypeScript definitions
│       ├── public/            ← static assets (images)
│       ├── Procfile           ← web: npm start
│       ├── package.json       ← Next.js 16, React 19, TypeScript 6, Node 24
│       ├── tsconfig.json      ← extends ../../tooling/tsconfig/nextjs.json
│       ├── next.config.ts
│       ├── .env.example
│       └── CLAUDE.md
│
├── packages/
│   ├── types/                 ← @soreto/types
│   │   ├── src/
│   │   │   ├── entities/      ← Campaign, Order, Reward, SharedUrl, User
│   │   │   ├── enums/         ← RoleName, OrderStatus, RewardStatus
│   │   │   ├── api/           ← ApiResponse, PaginatedResponse
│   │   │   └── index.ts
│   │   ├── package.json
│   │   └── tsconfig.json
│   │
│   ├── api-client/            ← @soreto/api-client
│   │   ├── src/
│   │   │   ├── client.ts      ← ReverbClient class
│   │   │   ├── postReward.ts
│   │   │   ├── notification.ts
│   │   │   ├── zendesk.ts
│   │   │   └── index.ts
│   │   ├── test/
│   │   ├── package.json
│   │   └── tsconfig.json
│   │
│   ├── constants/             ← @soreto/constants
│   │   ├── src/
│   │   │   ├── roles.ts
│   │   │   ├── countries.ts
│   │   │   ├── campaignTypes.ts
│   │   │   └── index.ts
│   │   ├── package.json
│   │   └── tsconfig.json
│   │
│   ├── config/                ← @soreto/config
│   │   ├── src/
│   │   │   ├── loader.ts      ← validated env loading
│   │   │   └── schemas/       ← per-app schemas
│   │   ├── package.json
│   │   └── tsconfig.json
│   │
│   └── auth-utils/            ← @soreto/auth-utils
│       ├── src/
│       │   ├── cookieHandler.ts   ← from soreto-cookie-jam
│       │   └── jwtDecode.ts
│       ├── package.json
│       └── tsconfig.json
│
├── tooling/
│   ├── eslint-config/         ← @soreto/eslint-config
│   │   ├── index.js
│   │   ├── next.js
│   │   ├── node.js
│   │   └── package.json
│   │
│   ├── tsconfig/              ← @soreto/tsconfig
│   │   ├── base.json
│   │   ├── nextjs.json
│   │   ├── node.json
│   │   └── package.json
│   │
│   └── jest-config/           ← @soreto/jest-config
│       ├── jest.base.js
│       ├── jest.node.js
│       ├── jest.react.js
│       └── package.json
│
├── docs/
│   ├── architecture/
│   │   ├── SYSTEM_OVERVIEW.md
│   │   ├── DATABASE_SCHEMA.md
│   │   ├── API_CONTRACT.md
│   │   └── AUTH_FLOW.md
│   ├── adr/
│   │   ├── 001-monorepo-migration.md
│   │   ├── 002-node-standardization.md
│   │   ├── 003-typescript-adoption.md
│   │   ├── 004-modernize-reverb-react.md
│   │   └── 005-seneca-decision.md
│   ├── spec/
│   │   ├── DOMAIN_MODEL.md
│   │   ├── API_ENDPOINTS.md
│   │   └── JOB_PROCESSORS.md
│   ├── migration/
│   │   ├── REVERB_REACT_PARITY.md
│   │   └── NODE_UPGRADE_TRACKER.md
│   └── dependencies/
│       ├── INTER_REPO_COUPLING.md
│       └── UPGRADE_BLOCKERS.md
│
├── turbo.json                 ← Turborepo task pipeline
├── package.json               ← Root workspace (engines, scripts, workspaces field)
├── .nvmrc                     ← 24 (Active LTS)
├── .env.example               ← Shared env var documentation
└── CLAUDE.md                  ← Root AI context document

System Architecture Diagram

╔═══════════════════════════════════════════════════════════════════╗
║                    SORETO PLATFORM                                ║
║                                                                   ║
║  ┌─────────────────┐     ┌───────────────────────────────────┐  ║
║  │   platform-ui   │────►│           platform-api            │  ║
║  │  (Next.js 16)   │     │  (Express 5, Node 24, TypeScript) │  ║
║  │  React 19        │     │                                   │  ║
║  │  shadcn/ui      │     │  Routes: /api/v1/*                │  ║
║  │  TypeScript     │     │  Auth: Passport (cookie/bearer)   │  ║
║  └─────────────────┘     └────────────┬──────────────────────┘  ║
║                                        │                          ║
║  ┌─────────────────┐                   │                          ║
║  │  legacy-portal  │     ┌─────────────▼─────────┐              ║
║  │  (→ Next.js 16) │     │     PostgreSQL          │              ║
║  │  reverb-react   │     │  schema: reverb         │              ║
║  └─────────────────┘     │  schema: zoe            │              ║
║                           └─────────────▲─────────┘              ║
║  ┌─────────────────┐                    │                         ║
║  │      zoe        │────────────────────┘                         ║
║  │  (Bull queues)  │     ┌──────────────────────┐               ║
║  │  TypeScript     │────►│        Redis          │               ║
║  │  12+ affiliate  │     │  Sessions + Bull Q    │               ║
║  │  processors     │     └──────────────────────┘               ║
║  └─────────────────┘                                              ║
║                                                                   ║
║  ─ ─ ─ ─ ─ ─ ─ ─ shared packages ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─  ║
║                                                                   ║
║  @soreto/types    @soreto/api-client    @soreto/constants         ║
║  @soreto/config   @soreto/auth-utils    @soreto/eslint-config     ║
╚═══════════════════════════════════════════════════════════════════╝

Key Configuration Files

npm-workspaces (in package.json)

{
  "workspaces": [
    "apps/*",
    "packages/*",
    "tooling/*"
  ]
}

turbo.json

{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": [".next/**", "dist/**"]
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": ["coverage/**"]
    },
    "lint": {
      "outputs": []
    },
    "typecheck": {
      "outputs": []
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

Root package.json

{
  "name": "soreto-platform",
  "private": true,
  "engines": {
    "node": ">=24.0.0",
    "npm": ">=10.0.0"
  },
  "workspaces": [
    "apps/*",
    "packages/*",
    "tooling/*"
  ],
  "scripts": {
    "build": "turbo build",
    "test": "turbo test",
    "lint": "turbo lint",
    "typecheck": "turbo typecheck",
    "dev": "turbo dev --parallel"
  }
}

Migration Sequence

Apps migrate into this structure one at a time, not simultaneously. See L — Migration Plan for the full phased sequence.

Order: 1. Create the monorepo scaffold (empty structure) 2. Migrate soreto-melissaapps/platform-ui/ (first — cleanest codebase) 3. Extract @soreto/types and @soreto/constants 4. Move reverb-reactapps/legacy-portal/ (stand up Next.js 16 scaffold; rewrite begins) 5. Migrate reverb-backendapps/platform-api/ 6. Migrate soreto-zoeapps/zoe/ (after AMD TypeScript refactor) 7. Extract @soreto/api-client (replace local_modules/reverb)


Legacy Portal: Modernization Target

apps/legacy-portal/ (reverb-react) is modernized, not frozen. The existing stack (Babel 6, Webpack 4, Node 10, React 17/dom 16.5 mismatch, react-router@3 + react-router-dom@4 mismatch) cannot be incrementally upgraded — the gaps are too large and compound. The correct path is a controlled rewrite to Next.js 16 + React 19 inside the monorepo.

Modernize to Next.js 16

Target stack:

Layer Current Target
Framework CRA-style Webpack 4 bundle Next.js 16.2.6 (App Router)
React 17.0.1 + react-dom 16.5.0 ⛔ React 19.2.6
Routing react-router@3 + react-router-dom@4 ⛔ react-router@7.15.1 (or Next.js file routing)
Language JavaScript/JSX TypeScript 6.0.3
State Redux 3.7.2 + Flux (mixed) React Context + Zustand or Redux Toolkit 2.x
Bundler Webpack 4.7.0 Turbopack (Next.js 16 default)
Node ~10.15.3 ⛔ 24 LTS
Styling PrimeReact 1.5.1 shadcn/ui (CLI v4) + Tailwind CSS 4.2.0 (matches platform-ui)
Build config .babelrc (Babel 6) tsconfig.json + SWC

Target structure after modernization:

apps/legacy-portal/    ← Next.js 16 App Router — no src/ directory
├── app/
│   ├── (admin)/       ← admin routes (guarded layout)
│   │   ├── dashboard/
│   │   ├── campaigns/
│   │   ├── users/
│   │   ├── reports/
│   │   └── layout.tsx
│   ├── (client)/      ← client routes (guarded layout)
│   │   ├── dashboard/
│   │   ├── campaigns/
│   │   └── layout.tsx
│   ├── layout.tsx     ← root layout
│   └── page.tsx       ← root redirect
├── components/        ← shadcn/ui components (CLI v4, vendored)
│   └── ui/            ← added via npx shadcn@latest add <component>
├── store/             ← Zustand stores (replaces Redux 3 + Flux)
├── service/           ← typed API calls via @soreto/api-client
├── types/             ← TypeScript definitions
├── public/            ← static assets
├── globals.css        ← Tailwind v4 @theme directive (no tailwind.config.js)
├── package.json       ← Next.js 16, React 19, TypeScript 6, Node 24
├── tsconfig.json      ← extends ../../tooling/tsconfig/nextjs.json
├── next.config.ts
├── components.json    ← shadcn/ui CLI config
├── .env.example
└── CLAUDE.md

Effort estimate: 6–10 weeks for one frontend engineer. This is a controlled rewrite — Babel 6, Flux, and the react-router v3/v4 dual installation cannot be incrementally migrated. The business logic (Redux action creators, API calls, reducers) can be preserved; the rendering and tooling layers are rewritten.

While the rewrite is in progress: Deploy a WAF (Cloudflare or AWS WAF) in front of the existing reverb-react Heroku app to compensate for Node 10 EOL exposure during the transition window.


Heroku Deployment Configuration

All four apps continue to run on Heroku. The monorepo changes where the code lives, not how it deploys. Each Heroku app gets one extra buildpack and one extra config var — that is the only Heroku-side change required.

Buildpack setup (per Heroku app)

Add heroku-buildpack-monorepo as the first buildpack, followed by the standard Node.js buildpack:

# platform-api
heroku buildpacks:clear -a <platform-api-app>
heroku buildpacks:add https://github.com/lstoll/heroku-buildpack-monorepo -a <platform-api-app>
heroku buildpacks:add heroku/nodejs -a <platform-api-app>
heroku config:set APP_BASE=apps/platform-api -a <platform-api-app>

# platform-ui
heroku buildpacks:clear -a <platform-ui-app>
heroku buildpacks:add https://github.com/lstoll/heroku-buildpack-monorepo -a <platform-ui-app>
heroku buildpacks:add heroku/nodejs -a <platform-ui-app>
heroku config:set APP_BASE=apps/platform-ui -a <platform-ui-app>

# legacy-portal
heroku buildpacks:clear -a <legacy-portal-app>
heroku buildpacks:add https://github.com/lstoll/heroku-buildpack-monorepo -a <legacy-portal-app>
heroku buildpacks:add heroku/nodejs -a <legacy-portal-app>
heroku config:set APP_BASE=apps/legacy-portal -a <legacy-portal-app>

# zoe
heroku buildpacks:clear -a <zoe-app>
heroku buildpacks:add https://github.com/lstoll/heroku-buildpack-monorepo -a <zoe-app>
heroku buildpacks:add heroku/nodejs -a <zoe-app>
heroku config:set APP_BASE=apps/zoe -a <zoe-app>

How the build works

1. heroku-buildpack-monorepo runs npm install from the REPO ROOT
   └─ resolves all @soreto/* workspace packages

2. heroku/nodejs buildpack changes into APP_BASE directory
   └─ runs the app's own build script (next build / tsc)
   └─ starts the app via its Procfile

Procfile location

Each Procfile lives inside its app directory — not at the monorepo root:

App Procfile location Content
platform-api apps/platform-api/Procfile web: node dist/server.js
platform-ui apps/platform-ui/Procfile web: npm start
legacy-portal apps/legacy-portal/Procfile web: npm start
zoe apps/zoe/Procfile worker: node dist/worker.js

devDependency pruning — one thing to watch

Heroku's Node.js buildpack prunes devDependencies after build. With npm workspaces, ensure each app's package.json lists its runtime @soreto/* dependencies directly in dependencies (not only devDependencies). If a workspace package is only in devDependencies, the prune step will drop it and the app will fail to start.

// apps/platform-ui/package.json — correct
{
  "dependencies": {
    "@soreto/types": "*",
    "@soreto/api-client": "*"
  }
}

What stays exactly the same

Item No change required
Number of Heroku apps Still 4 apps
Heroku pipelines and review apps Unchanged
Config vars / environment variables Per-app, unchanged
PostgreSQL, Redis add-ons Unchanged
Heroku region and dyno type Unchanged
Heroku CI (if used) Update app.json to set APP_BASE per pipeline

What Does Not Change

Item Reason
Database schemas (reverb, zoe) Schema changes are separate from repo structure
PostgreSQL / Redis / RabbitMQ Infrastructure stays the same
Heroku apps (count and names) 4 apps stay on Heroku — buildpack config is the only change
Existing git history Each app preserves its history via git subtree or history import
Business logic No business logic changes during the structural migration