Move business logic out of App Router into module packages, add boundary validation scripts, and keep all routes as thin re-exports without changing URLs or API behavior. Co-authored-by: Cursor <cursoragent@cursor.com>
16 KiB
TorbatYar Frontend — Enterprise Architecture Audit
Audit date: 2026-07-26
Scope: frontend/ source (excluding .next, node_modules)
Method: Read-only static analysis — no source files modified
Stack: Next.js 15.5.18 · React 19 · TypeScript 5.4 · Tailwind CSS 3.4
Executive Summary
The TorbatYar SuperApp frontend is a single Next.js monolith hosting the platform shell plus three mature business modules (Accounting, Beauty Business, Healthcare). The codebase contains ~481 TypeScript/TSX files and 350 route pages, organized around a consistent multi-tenant, portal-based architecture.
Overall readiness for long-term scaling: Moderate — structurally sound, operationally strained
| Dimension | Rating | Notes |
|---|---|---|
| Module isolation | ✅ Strong | No cross-domain imports between accounting/beauty/healthcare |
| Routing architecture | ✅ Good | App Router with portal layouts and BFF proxies |
| Design system | ⚠️ Mixed | components/ds/ is mature; parallel components/ui/ and per-module DS exist |
| Route vs component separation | ⚠️ Inconsistent | Beauty/healthcare use thin routes; accounting embeds logic in routes |
| Build health | ❌ Blocked | Production build fails TypeScript check (see Build Performance) |
| Dependency hygiene | ⚠️ Weak | Unused npm packages; 3 monolithic API clients (1,800+ lines each) |
| Observability / resilience | ⚠️ Missing | No middleware.ts, no route-level error.tsx / loading.tsx |
1. Repository Topology
frontend/
├── app/ # Next.js App Router (350 pages, 20 layouts, 3 API routes)
├── components/ # Shared + domain UI
│ ├── ds/ # Primary design system (11 files)
│ ├── ui/ # Legacy/marketing UI (6 files)
│ ├── providers/ # Query + color mode
│ ├── accounting/ # Accounting shell + screen aggregators
│ ├── beauty/ # Portal shells, design-system/, pages/
│ ├── healthcare/ # Portal shells, ui/, pages/
│ ├── admin/, auth/, settings/
│ └── *.tsx # Root-level guards, header, theme
├── hooks/ # 10 shared hooks
├── lib/ # 20 files — API clients, nav, auth, theme
├── styles/globals.css # CSS variables, RTL, module accents
├── public/ # Fonts, theme.config.json
├── scripts/
├── tailwind.config.ts
├── tsconfig.json # paths: @/* → ./*
└── next.config.mjs # env defaults, healthcare redirects
Path alias: Single alias @/* maps to project root. No scoped aliases (@accounting/*, @shared/*).
2. App Router Structure
2.1 Top-level segments
| Segment | ~Pages | Layout depth | Auth pattern |
|---|---|---|---|
/ (platform) |
6 | Root only | Mixed |
/accounting |
~130 | Root → Accounting | AuthGuard + tenant gate in layout |
/admin |
9 | Root → Admin | AdminAuthGuard (bypass on /admin/login) |
/beauty |
~100 | Root → pass-through → portal layouts | createPortalLayout() per portal |
/healthcare |
~100 | Root → pass-through → portal layouts | createPortalLayout() per portal |
/auth, /login, /register, /onboarding, /dashboard |
6 | Root only | Inline guards |
2.2 Route groups
Only one route group exists: app/beauty/(owner)/. Parentheses are invisible in URLs — /beauty/organizations resolves through (owner)/organizations/page.tsx.
Architecture smell: Beauty owner functionality is split across two URL trees:
app/beauty/(owner)/*— CRUD pages with inline form logic (~20 routes)app/beauty/owner/*— analytics, calendar, integrations via thin re-exports (~6 routes)
Both use createPortalLayout("owner") but live in separate folder hierarchies.
2.3 Dynamic routes (13)
| Pattern | Module |
|---|---|
/accounting/vouchers/[id] |
Accounting |
/admin/tenants/[id] |
Platform admin |
/beauty/mobile/[portal] |
Beauty |
/beauty/site/{center,salon,service,staff,tracking}/[id] |
Beauty public |
/healthcare/mobile/[portal] |
Healthcare |
/healthcare/site/{clinic,doctor,hospital,tracking}/[id] |
Healthcare public |
2.4 Missing App Router conventions
| File | Status | Impact |
|---|---|---|
middleware.ts |
Absent | No edge auth, tenant routing, or geo redirects |
loading.tsx |
Absent | No streaming skeletons at segment boundaries |
error.tsx |
Absent | Uncaught errors bubble to default Next.js error UI |
not-found.tsx |
Absent | Custom 404 not defined |
template.tsx |
Absent | No per-navigation remount behavior |
3. Layouts (20 total)
Root layout (app/layout.tsx)
- RTL Persian (
lang="fa" dir="rtl") - Provider stack:
ThemeProvider→ColorModeProvider→AppProviders - Global
SiteHeaderwraps all routes including module portals and public sites
Concern: Public marketing sites (/beauty/site, /healthcare/site) inherit the platform SiteHeader, which may not match intended public-site UX.
Module layout patterns
Pattern A — Dedicated module shell (Accounting, Admin):
app/layout.tsx → accounting/layout.tsx → AccountingShell + prefetch
app/layout.tsx → admin/layout.tsx → AdminShell
Pattern B — Pass-through + portal factory (Beauty, Healthcare):
app/layout.tsx → beauty/layout.tsx (pass-through)
→ beauty/{portal}/layout.tsx → createPortalLayout(id)
Pattern C — Public site layout:
app/layout.tsx → beauty/site/layout.tsx → PublicBeautyLayout
Portal factory (createPortalLayout) encapsulates:
AuthGuard- Tenant/onboarding gate via
useMe() - Domain portal shell with nav from
lib/*-portals.ts
4. API Routes (BFF Layer)
Three same-origin catch-all proxies under app/api/:
| Route | Upstream env | Default |
|---|---|---|
/api/accounting/[...path] |
ACCOUNTING_SERVICE_URL |
:8002 |
/api/beauty-business/[...path] |
BEAUTY_BUSINESS_SERVICE_URL |
:8011 |
/api/healthcare/[...path] |
HEALTHCARE_SERVICE_URL |
:8010 |
Methods: GET, POST, PATCH, PUT, DELETE
Forwarded headers: authorization, content-type, x-tenant-id, accept (+ x-patient-id for healthcare)
Gaps:
- No BFF for platform Core API (uses direct
NEXT_PUBLIC_BACKEND_URL) - No BFF for Identity/Keycloak
- No
/api/delivery/*despite backend delivery service existing - No rate limiting, request logging, or response caching at BFF layer
5. Business Modules
5.1 Accounting (/accounting)
- Architecture: Monolithic screen components + inline route pages
- Components:
AccountingShell,BusinessDocsPage,WorkflowScreens,DomainScreens,OperationalDocumentPage - API:
lib/accounting-api.ts(1,856 lines), consumed via/api/accounting/* - Forms: Heavy RHF + Zod (~20 files);
useFieldArrayin voucher flows - Route pattern: Most logic lives directly in
app/accounting/**/page.tsx(many 200–660 line files)
5.2 Beauty Business (/beauty)
- Architecture: Portal-based with page bundles
- Portals: owner, admin, customer, reception, staff, site (public), hub, auth, mobile
- Route pattern: ~75 routes are 2–3 line re-exports to
components/beauty/pages/{portal}.tsx - Owner CRUD: ~20 routes under
(owner)/contain inline page logic (forms, mutations) - API:
lib/beauty-business-api.ts(1,446 lines)
5.3 Healthcare (/healthcare)
- Architecture: Mirrors beauty portal pattern
- Portals: patient, doctor, reception, clinic, hospital, pharmacy, admin, site, hub, auth, mobile
- Route pattern: ~90 routes are thin re-exports to
components/healthcare/pages/ - Legacy redirects: 13 stub pages + 13
next.config.mjsredirects for URL compatibility - Delivery: Only accessible via
/healthcare/pharmacy-portal/delivery— no standalone delivery module
5.4 Platform Admin (/admin)
- Tenant/user/plan/feature management
- Uses
lib/api.ts(Core backend) directly, not BFF
6. Shared Infrastructure
6.1 Design system
Two parallel UI layers — see shared-components-report.md.
Primary: components/ds/ — CVA-based Button, FormField, DataTable, DatePicker (Jalali), Dialog, etc.
6.2 Theme
- White-label via CSS variables in
styles/globals.css - Runtime theme from
public/theme.config.jsonor tenant API (lib/theme.ts,useTheme) - Module accents:
--beauty-accent,--health-accent - Tailwind maps semantic tokens to CSS vars (
tailwind.config.ts)
6.3 State management
| Layer | Technology | Scope |
|---|---|---|
| Server/async state | TanStack Query v5 | Global (AppProviders) |
| Auth/session | Custom hooks + localStorage tokens | useMe, useAuth, lib/auth.ts |
| UI state | useState |
Local per component |
| Global UI | React Context | Color mode only (ColorModeProvider) |
No Redux, Zustand, Jotai, or URL state library.
Query defaults: staleTime 60s, gcTime 10min, retry 1, no refetch on focus/mount.
6.4 Hooks (hooks/ — 10 files)
| Hook | Usage count | Role |
|---|---|---|
useTenantId |
~67 | Tenant scoping for API calls |
useMe |
~7 | Session/user/workspace |
useBeautyLookups / useHealthcareLookups |
~6–7 | Cached dropdown data |
useHealthcarePatientContext / useHealthcareDoctorContext |
1 each | Portal identity resolution |
6.5 Forms
- Stack: react-hook-form + @hookform/resolvers + zod
- Accounting: Mature — Controller for MoneyInput/DatePicker, useFieldArray for vouchers
- Beauty owner: CRUD dialogs with zodResolver
- Healthcare: Display-heavy; minimal form usage
- No FormProvider pattern;
FormFieldwrapper from DS used instead
6.6 Tables
- Installed:
@tanstack/react-table— zero imports in source - Actual: Custom
DataTableincomponents/ds/Page.tsx— simple column/row renderer - Beauty wrapper:
BeautyTablePageadds client-side search
6.7 Charts
- Installed:
recharts— zero imports in source - Analytics routes exist; chart rendering not implemented
7. Route Implementation Patterns
Thin re-export (Beauty/Healthcare — ~175 routes)
// app/beauty/customer/wallet/page.tsx
export { CustomerWallet as default } from "@/components/beauty/pages/customer";
Benefits: Clean URL-to-component mapping, page bundles co-locate related screens
Risks: Large page bundle files (753 lines in beauty/pages/public.tsx)
Inline page logic (Accounting — ~130 routes)
Most accounting routes are "use client" components with queries, forms, and mutations inline. Some delegate to shared screen components (WorkflowScreens, DomainScreens) via 3–25 line wrappers.
Hybrid (Beauty owner CRUD — ~20 routes)
(owner)/* pages contain full CRUD implementation inline rather than delegating to page bundles like other beauty portals.
8. Architecture Violations & Smells
Critical
| Issue | Location | Severity |
|---|---|---|
| Production build fails TypeScript | app/accounting/purchase/returns/page.tsx:409 — loading prop on DS Button |
🔴 Blocker |
| Business logic in routes (accounting) | 644-line chart-of-accounts/page.tsx, 663-line reports/page.tsx |
🔴 Maintainability |
| Split owner portal (beauty) | (owner)/ vs owner/ folders |
🟡 Confusion |
Moderate
| Issue | Details |
|---|---|
| Dual Button/Badge systems | components/ui/ vs components/ds/ |
| Monolithic API clients | 3 files × 1,300–1,850 lines |
| Unused npm dependencies | @tanstack/react-table, recharts |
| Global SiteHeader on public sites | May leak platform chrome into marketing pages |
| No middleware | Auth/tenant checks duplicated in layouts |
| Dead code | 5 files with zero imports (see dependency-report.md) |
Low
| Issue | Details |
|---|---|
| Inconsistent module DS folder naming | beauty: design-system/ vs healthcare: ui/ |
Deprecated BeautyBusinessShell |
Still in codebase |
| Healthcare legacy redirect stubs | 13 client-side redirect pages duplicate config redirects |
9. Build Performance
Build command: npm run build (Next.js 15.5.18)
| Metric | Value |
|---|---|
| Webpack compile | ~55 seconds |
| Type check | Failed |
| Lint | Not reached (blocked by type error) |
Type error:
app/accounting/purchase/returns/page.tsx:409
Property 'loading' does not exist on ButtonProps (components/ds/Button.tsx)
Implications for scaling:
- 350 static/SSR pages in single build graph — compile time will grow linearly with new modules
- No module federation or package splitting
- All business modules share one
node_modulesand one build pipeline - Incremental compilation enabled (
tsconfig incremental: true) but full builds remain heavy
Recommendations (documented in migration plan):
- Fix type error immediately
- Consider route-group-level code splitting audit
- Evaluate
@next/bundle-analyzerbefore adding 4th business module
10. Scaling Readiness Assessment
What works well
- Clean domain boundaries — accounting, beauty, healthcare do not import each other's components or lib
- Consistent BFF pattern — browser never calls microservice origins directly for module APIs
- Portal factory pattern — new role-based portals can reuse
createPortalLayout - Shared DS adoption —
components/ds/used across all modules (~90+ import sites) - TanStack Query conventions — predictable cache keys per domain
- No circular dependencies — import graph analysis found zero cycles
What blocks enterprise scale
- Accounting route bloat — business logic not extracted from App Router pages
- API client monoliths — unmaintainable at 2,000+ methods per service
- Missing edge/middleware layer — auth and tenant resolution duplicated
- No error/loading boundaries — poor resilience UX at scale
- Build not green — CI cannot enforce quality gate
- Beauty/healthcare duplication — ~90% identical portal infrastructure copied twice
Verdict
The architecture is ready for adding routes and portals within existing modules but not ready for adding many new business modules without first establishing:
- Enforced module boundary lint rules
- Shared portal infrastructure abstraction
- Route-thin / component-thick convention across all modules
- Green build pipeline
11. Related Documents
| Document | Focus |
|---|---|
| frontend-migration-plan.md | Phased improvement roadmap |
| module-boundaries.md | Import rules and ownership |
| shared-components-report.md | DS inventory and duplicates |
| dependency-report.md | npm, API clients, dead code |
| import-report.md | Alias usage and violations |
| refactor-risk-analysis.md | Risk matrix for future changes |
Appendix: File Counts
| Category | Count |
|---|---|
Total .ts/.tsx source files |
~481 |
page.tsx routes |
350 |
layout.tsx |
20 |
| API route handlers | 3 |
components/ds/ files |
11 |
hooks/ files |
10 |
lib/ files |
20 |
@/ import statements |
756 |
| Relative import statements | 55 |
| Files > 300 lines | 20+ |
| Circular dependencies | 0 |