Scalable Directory Structures for Enterprise Web Apps
Structuring directory layouts in large-scale enterprise web applications requires moving beyond trivial framework defaults to architectures that promote modularity, maintainability, and domain encapsulation. As teams grow and codebases expand to hundreds of thousands of lines, traditional layer-first organizations (grouping purely by controllers, views, or services) fail due to high coupling and cognitive overhead. This guide outlines how enterprise engineering teams transition to feature-driven architectures, design self-contained modules, isolate shared logic, and use monorepos to scale applications efficiently.
The Pitfalls of Layer-First Grouping
Standard starter templates typically recommend grouping files by technical role:
src/
├── components/
├── controllers/
├── services/
├── views/
└── utils/
In large enterprise applications, this structure degrades quickly. Implementing a single business capability, such as updating user billing details, forces engineers to navigate half a dozen disparate folders. Discoverability declines, merge conflicts increase, and boundaries between unrelated business domains blur.
The Standard: Feature-First Architecture
Enterprise applications overwhelmingly favor a feature-first (or domain-driven) architecture. In this model, code is organized around business domains rather than technical responsibilities. Each domain acts as an autonomous module containing its own routing, UI, state, API integration, and tests.
src/
├── app/
│ ├── routes/
│ └── providers/
├── features/
│ ├── authentication/
│ │ ├── api/
│ │ ├── components/
│ │ ├── hooks/
│ │ ├── types/
│ │ └── index.ts
│ ├── billing/
│ │ ├── api/
│ │ ├── components/
│ │ ├── services/
│ │ ├── types/
│ │ └── index.ts
│ └── reporting/
├── shared/
│ ├── components/
│ ├── hooks/
│ ├── utils/
│ └── types/
Anatomical Breakdown of a Feature Module
Every feature directory represents a self-contained unit:
api/: Network requests, queries, mutations, and backend contract definitions specific to the feature.components/: UI elements used exclusively within this specific domain.hooks/orservices/: Business logic, state management, and orchestration.types/: Domain models, schemas, and static type declarations.index.ts(Public API): The entry point that explicitly defines what the feature exports to the rest of the application. Everything not exported here remains internal to the feature module.
Handling Cross-Cutting Concerns: Core vs. Shared
Enterprise layouts must explicitly define where code lives when it is
not tied to a single feature. Engineers typically separate this into two
distinct layers: core and shared.
The core/ Directory
The core directory holds global, singleton-like
dependencies that are instantiated once at application startup. This
logic is critical for app execution but is rarely touched during
day-to-day feature development.
- Global authentication interceptors and base HTTP clients.
- Top-level error boundaries and logging infrastructure.
- Route guards and base environment configurations.
The shared/ Directory
The shared (or common) directory contains
reusable, stateless building blocks that any feature can import.
- Primitive UI Components: Buttons, inputs, modals, and design-system tokens that contain zero domain knowledge.
- Pure Utility Functions: Date formatters, math utilities, and validation helpers.
- Global Types: Universal primitives shared across multiple bounded contexts.
Enterprise Monorepos and Workspace Layouts
When applications support multiple platforms (web, mobile, administrative portals), enterprises transition from a single monolithic directory to a workspace-based monorepo layout using tools like Turborepo, Nx, or pnpm workspaces.
repo-root/
├── apps/
│ ├── customer-portal/
│ │ ├── src/
│ │ └── package.json
│ └── admin-dashboard/
│ ├── src/
│ └── package.json
├── packages/
│ ├── ui-system/
│ │ ├── src/
│ │ └── package.json
│ ├── api-client/
│ │ ├── src/
│ │ └── package.json
│ ├── config-eslint/
│ └── tsconfig/
├── package.json
└── pnpm-workspace.yaml
This layout separates deployable targets (apps/) from
reusable libraries (packages/). The UI system, analytics
wrappers, and API client libraries are maintained independently with
distinct dependency graphs, preventing duplicated logic across
teams.
Enforcing Architectural Boundaries
A directory structure is only effective if its boundaries are mechanically enforced. Enterprise engineering teams implement strict automated safeguards to prevent layout degradation:
- Explicit Public Interfaces: Features must import
from another feature's entry point (
features/billing), never from its internal implementation details (features/billing/components/internal-card.tsx). - Linting Rules: ESLint plugins such as
eslint-plugin-boundariesoreslint-plugin-importenforce directional dependencies. For instance, code inshared/is strictly prohibited from importing code fromfeatures/. - Circular Dependency Detection: CI pipelines run automated checks to catch circular dependencies at build time, ensuring modules remain decoupled and independently testable.