Skip to main content

Codebase & Module Architecture

(How to structure frontend systems that scale in time and teams)

Architect rule:

If your codebase structure cannot explain itself,

your architecture exists only in your head.


Chapter 1 — Why Most Frontend Codebases Collapse

1.1 The Entropy Curve

Frontend codebases don’t fail because of:

  • bad developers

  • wrong frameworks

  • lack of tests

They fail because of structural entropy.

Symptoms:

  • “Where should this code live?”

  • “Just put it in shared”

  • “We’ll refactor later”

  • Fear of touching existing code

These are architectural failures, not human ones.


1.2 The Root Cause: Missing Boundaries

Without boundaries:

  • everything depends on everything

  • refactors ripple unpredictably

  • ownership dissolves

  • velocity drops as team size grows

Architect principle:

Boundaries are more important than abstractions.


Chapter 2 — Architecture ≠ Folder Structure (But It Must Be Visible)

2.1 The Architecture Visibility Problem

Architecture that is:

  • only documented

  • only discussed

  • only “understood”

…will drift.

Architects encode architecture:

  • in folder structure

  • in dependency rules

  • in TypeScript boundaries

  • in tooling constraints

If architecture is not enforced, it is optional.


2.2 The Three Valid Axes of Code Organization

Frontend code can be organized by:

  1. Layers (UI, domain, data)

  2. Features (auth, billing, dashboard)

  3. Technical concerns (hooks, utils, services)

Architects choose one primary axis

and allow others only locally.


Chapter 3 — Feature-Based Architecture (Architect Default)

3.1 Why Features Beat Layers at Scale

Layered architecture looks clean early:

/components
/hooks
/services
/utils

But it optimizes for code reuse, not change.

Feature-based architecture optimizes for:

  • ownership

  • locality

  • safe refactoring


3.2 Feature-Based Structure (Reference Model)

/app
/features
/auth
auth.routes.ts
auth.api.ts
auth.model.ts
auth.ui.tsx
/billing
/dashboard
/entities
/user
/order
/shared
/ui
/lib
/config

Each feature is:

  • cohesive

  • semi-independent

  • refactorable in isolation


3.3 Architect Rule: High Cohesion, Low Coupling

Inside a feature:

  • tight coupling is acceptable

Between features:

  • coupling must be explicit and minimal

This is how architects allow local complexity

without creating global chaos.


Chapter 4 — Dependency Direction (The Hidden Backbone)

4.1 The One Rule That Saves Codebases

Dependencies must point inward, never sideways.

This idea originates from classical architecture thinking (popularized by Clean Architecture), but frontend teams rarely enforce it.


4.2 Allowed Dependency Flow

app → features → entities →shared

Rules:

  • features may depend on entities

  • entities must never depend on features

  • shared depends on nothing

If this is violated, refactors become landmines.


4.3 Sideways Dependencies Are Poison

Anti-pattern:

  • auth importing from billing

  • dashboard reaching into orders/internal

Architect solution:

  • move shared logic to entities or shared

  • expose public APIs, hide internals


Chapter 5 — Public APIs vs Private Internals (Critical)

5.1 Modules Are Mini-Packages

Architects treat each module like an npm package:

  • public surface

  • private internals

  • stable contracts

Example:

/auth
index.ts ←public API
auth.api.ts
auth.internal.ts

Only index.ts is importable.


5.2 Why This Matters

Without public APIs:

  • consumers depend on internals

  • refactors break silently

  • ownership dissolves

Architect rule:

If it can be imported, it will be depended on.


Chapter 6 — TypeScript as an Architectural Tool

6.1 Types Are Not Just Safety Nets

Frontend architects use TypeScript to:

  • encode boundaries

  • prevent illegal access

  • document contracts

Types are architecture, not syntax.


6.2 Domain Types vs UI Types

Architect separation:

  • Domain types → business meaning

  • UI types → rendering concerns

Example:

typeUser = {
id:UserId
role:UserRole
}

typeUserCardProps = {
user:User
isSelected:boolean
}

UI never mutates domain meaning.


6.3 Branded & Opaque Types

Architects prevent misuse via types:

typeUserId =string & {__brand:'UserId' }

This:

  • prevents mixing IDs

  • catches bugs early

  • documents intent


Chapter 7 — The “Shared Utils” Trap

7.1 Why Shared Becomes a Graveyard

/shared often contains:

  • unrelated helpers

  • feature-specific hacks

  • abandoned code

This happens when:

  • ownership is unclear

  • boundaries are missing

  • reuse is premature


7.2 Architect Rules for Shared Code

Only move code to shared if:

  • used by multiple features

  • conceptually generic

  • ownership is clear

  • API is stable

Otherwise:

  • duplication is cheaper

Duplication is often less costly than wrong abstraction.


Chapter 8 — Refactoring Without Fear (Architect Strategy)

8.1 Refactoring Is Architectural Work

Refactoring is not:

  • cleanup

  • polishing

  • tech debt payment

It is re-aligning code with architecture.


8.2 Safe Refactoring Principles

Architects ensure:

  • boundaries are enforced

  • APIs are explicit

  • tests cover behavior, not structure

This allows:

  • large changes

  • incremental migrations

  • confidence


8.3 Strangler Patterns in Frontend

Architects migrate by:

  • introducing new boundaries

  • routing traffic gradually

  • isolating legacy code

Not by rewriting everything.


Chapter 9 — Tooling to Enforce Architecture

9.1 Linting as Policy

Architects use lint rules to:

  • enforce import boundaries

  • prevent forbidden dependencies

  • encode architectural rules

If violations compile, architecture is optional.


9.2 Monorepo Constraints

In monorepos:

  • enforce dependency graphs

  • restrict cross-package imports

  • define ownership clearly

This scales architecture beyond one team.


Chapter 10 — Exercises (Mandatory)

Exercise 1 — Boundary Mapping

Draw your current codebase.

Identify:

  • implicit boundaries

  • violated dependency directions


Exercise 2 — Public API Audit

Pick one feature.

List:

  • what should be public

  • what must be private

Create an index.ts accordingly.


Exercise 3 — Shared Code Cleanup

Review /shared.

For each item:

  • justify existence

  • assign ownership

  • delete or relocate if unclear


Chapter 11 — Part IV Summary

After Part IV, you should:

  • Design codebases for change, not elegance

  • Use features as the primary unit of architecture

  • Enforce dependency direction

  • Treat modules like packages

  • Use TypeScript as an architectural guardrail

If Part III answered “How data moves”