logo
21 Jan 2024

The File Structure Dilemma

How to organize your files?

Introduction and A Fact

In this discussion, we'll delve into file structure within the realm of frontend development, with a particular focus on React. React, at its core, is a library, not a framework. It primarily operates within the view layer, enabling its integration into applications like Angular for managing the UI. This integration involves referencing React through CDNs and creating components in JavaScript or JSX, which are then incorporated into Angular seamlessly.

React, distinct from Angular (specifically Angular version 1), doesn't adhere to the traditional Model-View-Controller (MVC) pattern. While Angular incorporates elements of MVC, React positions itself solely as a view layer library. This unique stance brings with it distinct concepts and approaches.

Given React's nature as a library, setting up a project involves careful planning and selection of additional libraries to supplement its capabilities. Tasks such as routing require specific libraries like React Router DOM. For state management, developers might opt for external libraries. The necessity to explore the React ecosystem for libraries catering to basic requirements is a fundamental aspect of working with React.

Ultimately, React doesn't function in isolation for production-grade projects. It necessitates a combination of complementary libraries to form a complete framework. The React documentation even suggests pairing with tools like Vite or NextJS. The essence of React lies in its coexistence with its ecosystem, proving that React alone isn't sufficient for building substantial projects.

The crux of the matter lies in determining the optimal file structure. Decisions regarding the hierarchical arrangement of files and their interrelationships require strategic planning from the outset, influenced by various factors. This article explores potential file organization strategies for your project, helping you make informed decisions about structuring your files.

Determining File Structure Factors

When deciding on your file structure for a project, several key factors come into play. The primary consideration is the project's size. This could range from a small, personal project to a complex one with multiple modules and numerous pages.

The second crucial factor is the team size working on the project. It could be a solo endeavor, a small team of two to five members, or larger groups divided into multiple teams. The team size significantly influences how the file structure should be organized, as larger teams require more systematic and clear file structures to ensure efficient collaboration.

It's important to note that there is no universally prescribed method for naming and organizing files. The approach can vary, often involving common folder names like 'components', 'hooks', 'pages', 'styles', and 'UI', among others.

In summary, the structure of your files will largely depend on the project's scale and the team's size. It could range from a simple setup with just a few folders to a complex structure dividing the project into sub-projects or micro-frontends for larger-scale applications.

Step by Step Approach

Basic File Structure

In the simplest form, such as those generated by boilerplates or CLI tools like create-react-app, Vite, or Next.js without using templates, the structure typically includes an entry point and a 'components' folder. This basic setup offers a foundational framework which can be expanded as needed.

Detailed Components Folder

The 'components' folder can be subdivided into two main categories:

  1. UI Components: This folder, possibly named 'ui', would contain stateless UI components like buttons, tabs, and accordions. These components may have internal states but don't rely on external states, functioning primarily through props.

  2. Logic or Business Components: The other subdivision, possibly named 'business' or 'logic', would house components that are more complex and intertwined with the application's logic or business aspects. An example would be a user card component with detailed information like avatar, name, email, and job description. These components might also interact with APIs and are reused across different parts of the project.

The categorization of components into 'ui' or 'logic/business' depends on their complexity and functionality. A UI-focused component like a simple button would fit into the 'ui' category, whereas a component involving more complex logic and data interaction would be more suited for the 'logic/business' category.

Components Folder

Hooks!

Alongside the 'components' folder within the source directory, another essential folder named "hooks" can be established. This folder is particularly relevant in React development, as it houses custom hooks. Ideally, these hooks should be universally applicable across the entire project. However, if a hook is intrinsically linked to a specific module, page, or substantial entity, it can be placed within that respective context. This flexibility underlines the absence of a rigid structure; adaptability is key, but with careful management to prevent disarray in the project.

The 'hooks' folder typically contains various custom hooks, each serving a distinct purpose. A common and highly useful example is the 'useClickOutside' hook. This hook is designed to detect clicks outside of a component, enabling specific actions in response. For instance, in a custom dropdown menu, this hook helps in automatically closing the menu when a click is detected outside its boundaries. The implementation of this hook may vary, but its function is clear from its name: it reacts to external clicks to close or modify elements accordingly. Such a custom hook, like 'useClickOutside', would be located within the 'hooks' folder, readily accessible for integration into components like dropdowns.

Organizing Styles

In our exploration of file organization within a React project, a crucial aspect to address is the 'styles' folder. This area, often vast and multifaceted, requires careful division. In my experience, particularly with projects utilizing SASS, the approach to organizing styles is twofold.

Firstly, there are global or shared styles that transcend individual components. These include mixins, functions, and overrides for third-party tools. Such styles don't belong to any single component but rather need a broader scope, hence the creation of a dedicated 'styles' folder. This higher-level repository ensures that overarching styles have a proper place within the project structure.

Working with SASS, I prefer adhering to the 7-1 SASS pattern, which effectively segments the 'styles' folder into seven sub-folders: 'base', 'components', 'pages', 'layout', 'vendors', 'utils', and 'themes'. This structured approach to managing styles is not just about segregating files arbitrarily; it follows a deliberate pattern to enhance organization and maintainability. This systematic arrangement allows any new team member to quickly grasp the structure and logic behind the style organization.

Regarding component-specific styles, the component architecture approach recommends a slightly different strategy. When a component has its own unique styles, and you're working with SASS, CSS, or LESS, it's advisable to place the style file directly adjacent to the component's logic file. For instance, a 'Button' component would have a corresponding folder named 'Button', containing two files: one with the React logic and the other with its associated styles. This methodology ensures that component-level styles are tightly coupled with their respective components, fostering a more modular and intuitive structure.

Overall, whether dealing with project-level styles in the 'styles' folder or component-specific styles alongside their logic files, the key is to establish a logical, documented structure. This approach not only aids in current project maintenance but also ensures that future contributors can easily understand, navigate, and extend the project's styling architecture.

Hooks and Styles Folders

The Average File Structure

In the realm of project organization, particularly for moderate-sized projects, there's a common practice of having a 'pages' folder. This folder is structured such that each subfolder represents a distinct page of the application, like the home page, about us page, contact us page, and so on. Each page is appropriately named and nested within the 'pages' folder, ensuring an organized and intuitive layout for the project's various pages.

Additionally, these pages often share a common layout, leading to the necessity of a 'layouts' folder. Positioned alongside the 'pages' folder, 'layouts' contains the shared layout templates used across different pages. For instance, a typical layout might include a header, footer, and a central content area. When a new page is created in a React-based project, it is composed with this main layout, incorporating the header and footer by default. The content area is what each specific page uniquely provides. Therefore, the introduction of a 'layouts' folder complements the 'pages' folder, streamlining the structure and design of each page.

Furthermore, some project structures distinguish between 'components' and 'containers'. This distinction harks back to the early days of React, where the concept of 'dumb' (stateless) components and 'smart' (stateful) components, or containers, was prevalent. In this context, a 'container' is typically a stateful component that represents a page or a significant part of it. This approach sees the 'container' as a large component comprising several smaller, stateless components, possibly including shared state elements. The choice between using a 'containers' folder versus a 'pages' folder varies among developers, but ultimately, both serve to organize larger, more complex components within the project.

In summary, the choice of folders like 'components', 'layouts', and 'containers' or 'pages' reflects different schools of thought in project organization. Each serves a specific purpose: 'pages' for individual pages, 'layouts' for shared design elements across pages, and 'containers' or 'components' for managing stateful and stateless elements respectively.

Pages, Containers and Layous

Feature-First (Module-First) File Structure

As a project expands, its structure often evolves to accommodate larger and more complex components, leading us to consider concepts like 'modules' and 'features'. In a structure often referred to as 'feature-first' or 'module-first', these two terms are essentially interchangeable. This approach significantly alters the traditional organization of folders such as components, styles, and hooks, which are now shared across various features or modules.

For instance, a universally used UI component, like a 'Button', would reside in a shared, accessible location outside specific modules. With the introduction of this 'feature-first' or 'module-first' approach, the existence of a separate 'pages' folder becomes redundant. This is because modules or features, as newly introduced entities, are more comprehensive and encompass aspects like pages.

To illustrate, let's take a module named 'CRM' (Customer Relationship Management), representing a business feature in a project. This module isn't just a single element; it's akin to a mini-project within the larger project. It includes multiple pages, reusable components specific to CRM, and its unique business and workflow processes.

When delving into a module like CRM, you'll discover a structure mirroring the main project source folder. Inside, there are subfolders named 'components', 'hooks', 'layouts', 'pages', and 'styles', all specific to the CRM module. Additionally, there's a central file, such as 'index.js', 'crm.js', 'jsx', or 'tsx' (depending on the programming language), serving as the module's entry point. Alongside this, a 'routes' file details the internal routes of the CRM module. Each module manages its own routing, reflecting its specific functionalities.

This 'module-first' or 'feature-first' structure effectively creates a self-contained ecosystem within each module, ensuring that every aspect of a module, from components to styles, is organized and easily navigable. This approach is particularly beneficial for large-scale projects, where clear segregation and modularization of features or business areas are essential for maintainability and scalability.

At times, it's practical to blend the 'feature-first' or 'module-first' approach with the use of 'containers' or 'pages'. This is particularly relevant when dealing with shared pages that are universally applicable across all modules or features. For instance, a 404 error page is a common requirement that doesn't warrant individual versions for each module or feature. It's more sensible to place such universal pages at the top level of the project structure.

Feature-First

Essential Folders

In the projects I develop, I typically consider incorporating several essential folders, each serving a distinct purpose in building a robust file structure. These folders are integral to answering common structural questions, like where to place utility functions.

  • helpers: This folder contains core helper functions used throughout the project. Examples include "httpClient", "XHRResolver", "Logger", etc., which provide fundamental support across various components.
  • utils: Here, I place small, pure functions that perform specific tasks. Functions like "convertHexToRGBA" and "buildQueryString" are examples of utility functions that handle precise operations.
  • data & services along with transformers: This setup aligns with the three-tier architecture principle, with transformers adding an extra layer for modifying or reshaping payload or response data. It's a structured way to manage data processing and service interaction.
  • types or interfaces: This folder is dedicated to storing types and interfaces, crucial for ensuring type safety and clear contract definitions within the code.
  • configs: For static configurations used throughout the project, such as default values for certain variables or a default date format, this folder is the designated place.
  • enums: I use the enums folder to store enumeration types, which helps in defining a set of named constants, improving the readability and maintainability of the code.
  • translations: If the project supports multiple languages, this folder contains the translation files necessary for internationalization.

These folder names and their usage can be adapted as needed. Depending on the specific requirements and scale of your project, you might choose to use all of these folders or only a selection. The flexibility to modify and tailor these folders to your project's needs is a key aspect of this structure.

Rare (Extrem) File Structure

The most complex file structure I'll discuss is for exceptionally large projects, often managed by multiple teams, each focusing on a distinct business segment. This setup is known as a micro front-end architecture.

Previously, I wrote an article about Micro-Frontend, explaining that this concept involves different teams handling separate parts of a product, from the front-end to the database. These distinct segments are then integrated under a container or shell to form a cohesive application. In the context of front-end development, each team might adopt different file structures, such as a focus on pages and layouts or adopting a feature-first or module-first approach. The choice of structure depends on the project's scale and the specific business area each team is responsible for.

Another scenario I covered in previous article involves managing a large project through a monorepo, which presents its unique file structure challenges. In a monorepo setup, there's typically a root folder named 'packages'. Within 'packages', you find various subfolders for different segments like 'helpers', 'utilities', 'CRM', 'project management', 'UI', and 'components'. The division of these packages is flexible, depending on the project's needs, and each package essentially functions as a standalone project. For example, expanding the 'CRM' folder under 'packages' reveals a complete project setup, with its components, hooks, styles, and other elements. If extracted from the monorepo, it should be able to operate independently.

In some cases, a monorepo might not be structured as a micro front-end but simply as a collection of packages. These packages could include reusable components, shared styles, or distinct projects. Each package, while conforming to the overarching monorepo structure, follows its internal organization. This could range from a basic setup with components and containers to more elaborate structures incorporating features or modules. For instance, a package named 'project management' might contain a 'features' folder, managing all aspects related to project management within that package.

In summary, the file structure in a micro front-end or a monorepo is multifaceted, catering to the complexity and scale of large projects. Each package or segment within this setup is organized in a way that allows it to function both as part of the larger whole and as an independent entity, ensuring modularity and flexibility in the project's architecture.

Packages

Outro

There is no universally accepted school of thought or definitive catalog dictating file structure in project development. Instead, the organization of files is largely influenced by factors such as the size and scope of the team, the business objectives, and the project's anticipated lifespan. This variability underscores the importance of collaboration between the front-end lead or senior front-end developers and the business and architectural teams. Such engagement is crucial for understanding the project's vision, pipeline, and end goals, which could range from a few months to several years.

The involvement of the front-end team from the outset is essential. It allows them to align their approach with the broader objectives, influencing key aspects like the file structure. Foreknowledge of the project's trajectory – whether it's adding features over four years or just one – is vital for designing a scalable and adaptable structure from day one. However, it's important to recognize that no structure is permanent. Flexibility and periodic adjustments are necessary, though frequent overhauls should be avoided to prevent disruptions.

The goal is to establish a file structure that is robust enough to last for several years, anticipating significant feature expansions. This proactive approach is preferable to a reactive one, where the structure becomes cumbersome and intertwined, necessitating major restructuring. As the project evolves, various factors, such as performance enhancements, team expansion, or shifting business requirements, may necessitate revisiting and modifying the file structure.

It's crucial to understand that a file structure is not static. It evolves, potentially changing every six months or even every three months, based on numerous variables: team dynamics, business goals, feature complexity, and team expansion. These factors dictate how the file structure should be adapted or divided to create a conducive environment for efficient work and future extensions.

In summary, while there is no one-size-fits-all approach to file structuring in project development, the key lies in maintaining a balance between flexibility and foresight. The structure should be adaptable to accommodate changes yet thoughtfully planned to sustain the project's growth and evolution over time.