Frontend Architecture

SOLID View/ViewModel pattern, module structure, and the connector pattern for Next.js integration.

The SCRIPE frontend is built with Next.js 16 (App Router) following a strict SOLID View/ViewModel pattern. Every page is composed of a pure UI View that delegates all logic to ViewModel hooks. This separation ensures testability, reusability, and maintainability.

SOLID View/ViewModel Pattern

The SOLID pattern ensures each piece of the UI has a single responsibility. Views render JSX, ViewModels manage state and logic, and Components provide reusable UI sections.

PrincipleApplication in SCRIPE
S — Single ResponsibilityEach ViewModel handles ONE concern (filter, stats, table)
O — Open/ClosedBase hooks extended via composition, not modification
L — Liskov SubstitutionAll ViewModels return consistent typed interfaces
I — Interface SegregationComponents receive only the props they need
D — Dependency InversionViews depend on ViewModel interfaces, not implementations

View Rules

  • View SHOULD

    • Max ~200 lines of JSX
    • Zero useState, zero useEffect
    • Only destructures props from ViewModel
    • Returns JSX with component composition
    • Imports section components for layout
  • View SHOULD NOT

    • No business logic in views
    • No API calls or data transformations
    • No direct state management
    • No inline styles with logic
    • No conditional data fetching

View Example

UserManagementView.tsx — Pure UI ViewTypeScript
'use client';

/**
 * Presentation UI component rendering the user management view.
 * Arranges layout boundaries and accessibility targets (WCAG, tab index) using the core design library (@core/ui/*). Coordinates text fields, submit indicators, and validation warning messages.
 */
export function UserManagementView() {
  const vm = useUserManagementViewModel();

  return (
    <div>
      <h1>{vm.title}</h1>
      <FilterSection {...vm.filters} />
      <Spacer />
      <StatisticsSection {...vm.statistics} />
      <GenericCrudView {...vm.table} columns={vm.columns} />
    </div>
  );
}
// ~15 lines. Views are this short. Always.

ViewModel Rules

ViewModels are React hooks that contain all business logic. They compose section-specific ViewModels (statistics, filters, table) and return typed interfaces consumed by Views.

useUserManagementViewModel.ts — OrchestratorTypeScript
export function useUserManagementViewModel() {
  // Compose section ViewModels (Single Responsibility)
  const statistics = useStatisticsViewModel();
  const filters = useFilterViewModel();
  const table = useCrudViewModel(crudConfig);
  const blockAction = useBlockUserAction();

  // Columns defined HERE, not in View or Component
  const columns = useMemo(() => [
    column.index("No"),
    column.text("name", "Name"),
    column.text("email", "Email"),
    column.status("status", "Status", statusMap),
    column.switch("block", "Block", blockAction),
    column.date("createdAt", "Created", { locale: "en-GB" }),
  ], [blockAction]);

  return {
    title: t("userManagement.title"),
    statistics,
    filters,
    table,
    columns,
  };
}

Module File Structure

Standard Frontend Module StructureText
module/
├── di.ts                     # Module DI Container
├── index.ts                  # Public API exports
└── src/
    ├── domain/               # Business Logic (Pure TypeScript)
    │   ├── entities/         # Zod schemas + business rules
    │   └── interfaces/       # Repository contracts
    │
    ├── data/                 # Data Access Layer
    │   ├── models/           # API DTOs (raw response shapes)
    │   ├── mappers/          # DTO ↔ Entity transformations
    │   └── repositories/     # Interface implementations
    │
    └── presentation/         # UI Layer (SOLID Pattern)
        ├── viewmodels/       # Section ViewModels + Orchestrator
        ├── views/            # Pure UI Pages (~200 lines max)
        └── components/       # Section Components

Connector Pattern

The connector pattern separates Next.js App Router pages (Server Components) from module Views (Client Components). Pages in src/app/ are thin connectors that import and render module Views. They handle routing, metadata, and URL params only.

TypeScript
// src/app/(modules)/admin/user-management/page.tsx
import { Metadata } from 'next';
import { UserManagementView } from '@modules/admin/user-management';

export const metadata: Metadata = {
  title: 'User Management | SCRIPE',
};

export default function Page() {
  return <UserManagementView />;
}
// That's it. Server component. No logic. Just connects.