In frontend development, maintaining a clean and scalable codebase is critical, especially as projects grow in size. Without a proper structure, the code can become disorganized, hard to navigate, and difficult to maintain. This Article introduces a modular architecture that addresses these challenges, ensuring better organization, reusability, and scalability in your projects.
Problems with Poor Structure
Many growing projects suffer from the following issues:
- Difficult navigation: Files are scattered across the project with no clear organization, making it hard to find or modify code.
- Hard to scale: Adding new features leads to bloated files and increased dependencies between different parts of the app.
- Low maintainability: Changes in one part of the code can cause unexpected issues elsewhere due to tight coupling.
- Duplicate code: Lack of a centralized system leads to repeated functionality across the codebase.
Modular Architecture: The Solution
A modular architecture helps address these problems by organizing your project into self-contained feature modules. Each module contains all the necessary components, logic, and assets related to a specific feature, promoting isolation, reusability, and scalability.
Key Principles
-
Kebab-case Naming Convention: Use
kebab-case
for all directories and filenames to ensure consistency across the codebase. - Encapsulate All Components in Directories: Every component must have its own directory, even if it only contains a single file. This keeps components isolated and prepared for future expansion.
- Feature-based Modules: Organize the project by features (modules), where each module contains its own components, hooks, stores, and more.
-
Shared Resources: Centralize reusable components, hooks, services, and utilities in a
shared
directory. - Strict Module Boundaries: Modules should only access shared resources and never reach into other modules.
Directory Structure
Below is the base directory structure, showing how the project is organized. This structure can be scaled as more modules are added.
๐ src/
โโโ ๐ infrastructure/
โ โโโ ๐ models/
โ โโโ ๐ theme/
โ
โโโ ๐ modules/
โ โโโ ๐ dashboard/
โ โ โโโ ๐ components/
โ โ โ โโโ ๐ dashboard-header/
โ โ โ โ โโโ index.tsx
โ โ โ โโโ ๐ dashboard-main/
โ โ โ โ โโโ index.tsx
โ โ โโโ ๐ hooks/
โ โ โ โโโ use-dashboard-data.ts
โ โ โโโ ๐ stores/
โ โ โ โโโ dashboard-store.ts
โ โ โโโ ๐ constants/
โ โ โ โโโ dashboard-constants.ts
โ โ โโโ ๐ services/
โ โ โ โโโ dashboard-service.ts
โ โ โโโ ๐ data/
โ โ โ โโโ dashboard-data.ts
โ โ โโโ ๐ index.tsx
โ โ
โ โโโ ๐ user-management/
โ โ โโโ ๐ components/
โ โ โ โโโ ๐ user-list/
โ โ โ โ โโโ index.tsx
โ โ โ โโโ ๐ user-profile/
โ โ โ โ โโโ index.tsx
โ โ โโโ ๐ hooks/
โ โ โ โโโ use-user-management.ts
โ โ โโโ ๐ stores/
โ โ โ โโโ user-store.ts
โ โ โโโ ๐ constants/
โ โ โ โโโ user-management-constants.ts
โ โ โโโ ๐ services/
โ โ โ โโโ user-service.ts
โ โ โโโ ๐ data/
โ โ โ โโโ user-data.ts
โ โ โโโ ๐ index.tsx
โ
โโโ ๐ shared/
โ โโโ ๐ components/
โ โ โโโ ๐ button/
โ โ โ โโโ index.tsx
โ โโโ ๐ hooks/
โ โ โโโ use-auth.ts
โ โโโ ๐ utils/
โ โ โโโ date-formatter.ts
โ โโโ ๐ stores/
โ โ โโโ app-store.ts
โ โโโ ๐ constants/
โ โ โโโ app-constants.ts
โ โโโ ๐ services/
โ โ โโโ api-service.ts
โ โโโ ๐ data/
โ โ โโโ app-data.ts
Explanation of the Structure
1. Infrastructure
The infrastructure
folder contains low-level, foundational resources that support the project. These could include models, themes, or configurations shared across the app.
- Models: Define entities or data structures.
- Theme: Global theming or styling settings for consistent UI across the application.
2. Modules
Each module is a self-contained folder that represents a specific feature of the application. Each module encapsulates:
- Components: Contains all UI elements for the module. Each component has its own folder, ensuring separation and future extensibility.
- Hooks: Custom React hooks related to the moduleโs logic.
- Stores: State management files related to this module.
- Constants: Static data like enums or string values related to the module.
- Services: API calls or other asynchronous logic specific to the module.
- Data: Local or mock data related to the module for easy testing or seeding purposes.
Example: Dashboard Module
๐ dashboard/
โโโ ๐ components/
โ โโโ ๐ dashboard-header/
โ โ โโโ index.tsx # Component rendering dashboard's header section
โ โโโ ๐ dashboard-main/
โ โ โโโ index.tsx # Component rendering dashboard's main section
โโโ ๐ hooks/
โ โโโ use-dashboard-data.ts # Custom hook for fetching and managing dashboard data
โโโ ๐ stores/
โ โโโ dashboard-store.ts # State management for dashboard data (e.g., Zustand, Redux)
โโโ ๐ constants/
โ โโโ dashboard-constants.ts # Static values like action types or UI text
โโโ ๐ services/
โ โโโ dashboard-service.ts # Service handling API calls or business logic
โโโ ๐ data/
โ โโโ dashboard-data.ts # Local or mock data for dashboard
โโโ ๐ index.tsx # Entry point for the dashboard module, used in routing
3. Shared Resources
The shared
folder contains reusable resources that can be accessed by any module. These include:
- Components: UI components like buttons, inputs, or modals that can be used across multiple modules.
- Hooks: Reusable logic, such as authentication or form handling.
- Utils: Utility functions like date formatters, validation, etc.
- Stores: Global state management that is shared across modules.
- Constants: Application-wide constants such as routes, configuration values, or environment variables.
- Services: Common services for interacting with APIs, local storage, or global functionality.
- Data: Shared or global data that can be accessed across modules.
Example: Shared Components
๐ shared/
โโโ ๐ components/
โ โโโ ๐ button/
โ โ โโโ index.tsx # A reusable button component used across multiple modules
Additional Tips
1. Group Related Modules
For larger projects, consider grouping related modules. For example, an admin
section could contain modules like dashboard
, user-management
, and settings
, all under a common admin
directory:
๐ admin/
โโโ ๐ dashboard/
โโโ ๐ user-management/
โโโ ๐ settings/
2. Module-specific Layouts
Some modules may require their own layout components. For instance, an admin panel may have a different sidebar or header from the rest of the app. In this case, the module can have its own layout.tsx
file.
๐ admin/
โโโ ๐ layout/
โ โโโ index.tsx # Custom layout for the admin module
3. Lazy Loading for Performance
Modules can be lazily loaded to improve performance, ensuring that only the necessary code is downloaded when a specific route is visited.
// Lazy load a module with React Router
const Dashboard = React.lazy(() => import('./modules/dashboard'));
const UserManagement = React.lazy(() => import('./modules/user-management'));
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/user-management" element={<UserManagement />} />
Conclusion
A well-structured modular architecture is key to building scalable, maintainable, and efficient frontend applications. By enforcing kebab-case naming conventions, encapsulating all components within directories, and adhering to strict module boundaries, you can create a clean and reusable codebase that grows with your project.
With this approach, your project remains organized, easy to navigate, and ready for future expansionโno matter how large it becomes!
Top comments (0)