Architecture

Our architecture prioritizes reusability, modularity, and a world-class developer experience. It’s designed for a small, async, part‑time team to add value safely and predictably. Every feature is composed from small, typed building blocks and mirrors a clear URL-to-directory structure.

Core Principles

Our design decisions are guided by a few key principles:

  • Component-Driven Modularity – Inspired by the philosophy of shadcn/ui, we provide a codebase of reusable, customizable components at their most minimal, essential state. Every piece of functionality is an "atomic" building block designed for composition.
  • Superior Developer Experience – We believe that an intuitive and predictable structure is key to productivity. Our architecture is designed to be easily understood, navigated, and extended.
  • Feature-Based & Composable – The system is built as a collection of features, enabling a micro-services and micro-frontends approach. Independent components and services can be developed, deployed, and composed to build complex applications.
  • Serverless-First – Deploy on Vercel; Neon (Postgres) for serverless DB.
  • Type-Safety by Default – Prisma + Zod + TypeScript across the stack.
  • Async-First – Small PRs, documented decisions, steady progress.

The Composition Hierarchy: From Radix UI to Masterpieces

Our methodology is built on a foundation of proven, composable building blocks that scale from primitive components to complete applications. We leverage the power of the shadcn ecosystem to create a systematic approach to building features.

Foundation Layer: Radix UI → shadcn/ui → shadcn Ecosystem

  1. Radix UI – The primitive foundation providing unstyled, accessible components
  2. shadcn/ui – Built on top of Radix UI, providing styled, customizable components
  3. shadcn Ecosystem – Community-driven components and libraries inspired by shadcn/ui patterns

Composition Hierarchy: From UI to Masterpieces

  1. UI – shadcn/ui components serve as our base UI layer, providing consistent styling and behavior
  2. Atoms – Two or more shadcn/ui components (or components from the shadcn ecosystem) combined to create atomic building blocks
  3. Templates – Reusable layouts and component compositions that solve common UI patterns
  4. Blocks – Interactive features combining Templates with client-side logic (hooks.ts, validation.ts)
  5. Micro – Encapsulated features with backend logic (actions.ts), Prisma access, validation, and UI
  6. Apps – Complete applications composed of several Micro features

This layered approach ensures that every component is built on solid foundations while maintaining the flexibility to create complex, feature-rich applications. Each level adds value and functionality while remaining composable and reusable.

Project Structure: A Feature-Based, Mirror-Pattern Approach

Our project structure follows a predictable, mirror-pattern architecture that enhances code discoverability and creates an intuitive mental model for developers.

Philosophy: URL-to-Directory Mapping

Every URL route has a corresponding, mirrored directory structure for its component logic. If you can see a URL, you know exactly where to find its code.

URL: /abc

mirrors to:

src/app/abc/          (Next.js App Router files)
src/components/abc/   (All related component logic)

Directory Structure

Top-Level Structure

project-root/
src/Source code directory
app/Next.js App Router (Routing & Layouts)
abc/URL route: /abc
components/Component Logic (Mirrors app structure)
abc/Mirrors app/abc/
atom/Atomic UI components
template/Reusable layout templates
ui/Base UI components (shadcn/ui)
lib/Shared utilities & functions
styles/Global styles and CSS

Deeper Layer Structure

src/
app/
page.tsxRoot page component
layout.tsxRoot layout component
abc/URL route: /abc
page.tsxABC main page component
layout.tsxABC route-specific layout
loading.tsxABC loading state component
components/
abc/Mirrors app/abc/
content.tsxABC UI content components
action.tsServer actions & API calls
constant.tsArrays, enums, static data
validation.tsZod schemas & validation logic
type.tsTypeScript interfaces & types
form.tsxForm components
card.tsxCard components
all.tsxAll list
featured.tsxFeatured list
detail.tsxDetail content
util.tsUtility functions
column.tsxTable columns
use-abc.tsCustom React hooks
README.mdREADME file
ISSUE.mdISSUE file

Standardized File Patterns

To ensure consistency, each feature directory follows standardized naming conventions. A CLI tool helps scaffold these files automatically, enforcing the pattern across the codebase.

FilePurpose
content.tsxCompose feature/page UI: headings, sections, layout orchestration.
action.tsServer actions & API calls: validate, scope tenant, mutate.
constant.tsEnums, option lists, labels, defaults for the feature.
validation.tsZod schemas & refinements; parse and infer types.
type.tsDomain and UI types; generic helpers for forms/tables.
form.tsxTyped forms (RHF) with resolvers and submit handling.
card.tsxCard components for KPIs, summaries, quick actions.
all.tsxList view with table, filters, pagination.
featured.tsxCurated feature list showcasing selections.
detail.tsxDetail view with sections, relations, actions.
util.tsPure utilities and mappers used in the feature.
column.tsxTyped Table column builders and cell renderers.
use-abc.tsFeature hooks: fetching, mutations, derived state.
README.mdFeature README: purpose, APIs, decisions.
ISSUE.mdKnown issues and follow-ups for the feature.

Generics in Practice

We leverage TypeScript Generics extensively across type.ts, use-abc.ts, column.tsx, validation.ts, and forms to keep logic reusable and type-safe without duplication. See the dedicated guide for patterns and examples: Pattern docs.

System Layers & Data Flow

This structured, component-based approach integrates seamlessly with our technical system layers.

  1. Presentation Layer (Next.js/React) – Renders the components defined in form.tsx, cards.tsx, and content.tsx.
  2. Application & API Layer (Vercel) – Executes the logic from actions.ts, validates data with schemas from validation.ts, and handles authentication with Auth.js.
  3. Data & Persistence Layer (Neon Postgres/Prisma) – Interacts with the database via Prisma schemas and types, scoping queries by schoolId.
  4. Observability & Ops – Logging, metrics, backups, and runbooks sized for a small team.
  5. Developer Tooling – ESLint, Prettier, pnpm scripts; shadcn/ui conventions.

A Typical Interaction

  1. A user interacts with a component from form.tsx on the Next.js frontend, triggering a Server Action from actions.ts.
  2. The request payload is validated by a Zod schema from validation.ts.
  3. The serverless function uses the type-safe Prisma client to query Neon Postgres.
  4. Observability captures request ID and schoolId for traceability.
  5. The result is streamed back and managed by a hook from use-abc.ts, efficiently updating the UI.

This reorganized document follows the same styling and structural conventions used across the documentation, ensuring a cohesive reading experience.