ADR-0025: Onboarding Subdomain — Two Vite Configs vs Separate Project
- Status: Accepted
- Date: 2026-07-13
- Deciders: User + agent research session
- Issues: #210
Context
SDD-147 (Q2) specifies that the public onboarding portal (/onboarding/:hash) should be moved to a separate subdomain (onboard.neemias.app) after MVP. Currently the onboarding flow lives at app/src/modules/onboarding/ within the same SPA and bundle.
The onboarding form is a lightweight React + Tailwind app with no dependencies on MUI, recharts, auth contexts, or dashboard code. It calls existing public API routes at api.neemias.app.
Two approaches were considered:
A) Two Vite configs in the same app/ directory — a second config file (vite.onboarding.config.ts) with its own HTML entry, built via vite build --config vite.onboarding.config.ts, output to dist-onboarding/.
B) Separate pnpm workspace project — apps/onboarding/ (or app/onboarding/) with its own package.json, tsconfig.json, vite.config.ts, listed in pnpm-workspace.yaml.
Decision
Approach A: Two Vite configs in the same app/ directory.
Architecture
onboard.neemias.app ──▶ Cloudflare Pages (build from app/ + vite.onboarding.config.ts)
│
└── fetch("/api/v1/onboarding/:hash/*") ──▶ api.neemias.app (Worker existente)Key details
| Aspect | Choice |
|---|---|
| Vite config | app/vite.onboarding.config.ts |
| HTML entry | app/onboarding-index.html |
| React entry | app/src/onboarding/main.tsx |
| Output dir | dist-onboarding/ |
| Cloudflare Pages root | app/ |
| Cloudflare Pages build command | pnpm build:onboarding |
| Domain | onboard.neemias.app |
Why not Approach B (separate project)
A separate pnpm workspace package would be the canonical choice in a large, multi-team monorepo using Turborepo conventions (apps/*). However, at Neemias's current scale:
- Dependency lists are identical — onboarding uses only React + Tailwind, already in
app/package.json. A separatepackage.jsonduplicates what's already there. - No net benefit in bundle size — Vite tree-shakes each build independently, so the
dist-onboarding/bundle won't contain MUI or recharts regardless of what's inpackage.json. - Higher maintenance surface — a separate
tsconfig.json,tailwind.config.*, and dev tooling per sub-site must be kept in sync. - Simple upgrade path — if onboarding ever needs its own dependencies or grows to warrant full separation, migration to
apps/onboarding/is a copy-and-register operation.
Cloudflare Pages configuration
| Setting | Main SPA | Onboarding |
|---|---|---|
| Project name | neemias-app | neemias-onboarding |
| Root directory | app/ | app/ |
| Build command | pnpm build | pnpm build:onboarding |
| Build output | dist | dist-onboarding |
| Custom domain | app.neemias.app | onboard.neemias.app |
pnpm-workspace.yaml
No changes needed. Both builds resolve dependencies from the existing "app" workspace entry.
Consequences
- Positive: Bundle reduction — main SPA no longer includes onboarding components (~8 files, form libraries).
- Positive: Isolation — onboarding build failures don't block main SPA deploys, and vice versa.
- Positive: Lower dev overhead —
pnpm devstays unchanged; onboarding dev viapnpm dev:onboarding. - Positive: No CORS changes needed — API routes are already public (no auth middleware).
- Neutral: Two Cloudflare Pages projects to manage instead of one. Build watch paths should be configured to avoid unnecessary rebuilds.
- Negative: If onboarding later needs different dependencies or build tools, the two-config approach becomes a constraint and full separation will be needed.
Alternatives Considered
| Alternative | Rejected because |
|---|---|
Separate apps/onboarding/ pnpm workspace package | Overhead without benefit at current scale. Documented as the upgrade path. |
Single Vite config with rollupOptions.input | Produces a single build with multiple entry points — not deployable to separate subdomains. |
Sub-path routing on the same domain (app.neemias.app/onboarding) | SDD-147 explicitly specifies a separate subdomain for post-MVP. |
Upgrade Path
When onboarding grows to warrant full separation:
- Copy
vite.onboarding.config.tsintoapps/onboarding/vite.config.ts - Create
apps/onboarding/package.jsonwith its own dependency list - Create
apps/onboarding/tsconfig.json(extends shared base) - Add
apps/onboardingtopnpm-workspace.yaml - Move
app/src/onboarding/toapps/onboarding/src/ - Update Cloudflare Pages root directory to
apps/onboarding/ - Remove
vite.onboarding.config.tsandonboarding-index.htmlfromapp/
References
- Issue #210 — Onboarding subdomain post-MVP
- Research: onboarding-subdomain-approach.md
- Vite docs:
--configflag (https://vite.dev/config/) - Vite docs: Multi-page app (https://vite.dev/guide/build.html#multi-page-app)
- Cloudflare Pages docs: Monorepos (https://developers.cloudflare.com/pages/configuration/monorepos/)
- SDD section 12 — Module Boundaries