Organize your codebase to reflect your UI
Projects using traditional structure become increasingly confusing code as the codebase grows. Splitting the code by layers (components/
, redux/
, hooks/
, utils/
…) works well for small projects but shows its limitations in larger applications.
The problem
When debugging in an unfamiliar codebase I like to use the UI as a reference to find the corresponding code.
I have to fix the a bug causing the app to ignore accents in the user’s first name. Using the “Save profile” button test to find I find the component in
src/components/
.As it turns out a request payload not escaping its content is the origin of the bug. I need to find in which file the request is made. In this project the requests are all written in the
src/api/
folder.The call isn’t made from the UI and after some research I find that the API calls live
src/api/
. The action called by the button does not create the payload. Instead it triggers a thunk that fetches the form state from redux.I found the
updateUser
call but realize that the payload is actually created in a redux action. I Now have to navigate `src/redux/actions` and find out where the payload is constructed.
Structure of the project
/src
├── components/
│ ├── UserPage.js
│ ├── EditUserTab.js
│ └── EditUserForm.js
├── api/
│ └── userApi.js
└── redux/
├── reducers/
│ ├── userReducer.js
├── actions/
│ ├── userActions.js
└── selectors/
└── userSelectors.js
In the end that was a lof ot back and forth to find some piece of code. It’s not the bane of my life but it could be much simpler!
Mirroring your UI
I started with the Java’s “package by feature” method and adapted it to frontend code it by following two blanket rules:
- The React code’s folder structure should follow the visual hierarchy of the application.
- The data management structure should be organised according to the business model.
Instead of scattering functionally correlated code in technical layers I sort it by pages, UI sections or features.
I often start by splitting the src/
folder in four main subfolders: features/
, pages/
, shared/
and data/
. I usually place a test-utils
folder there too but we will ignore it today.
/src
├── data/
├── features/
├── pages/
└── shared/
API calls, state management and Typescript interfaces go in the data/
folder. Everything is grouped according to the application’s business models.
/src/data
├── admin/
├── user/
└── organisation/
The features/
folder include large pieces of code. They are self-contained and represents one functional aspect of the code.
It is more than a display component and is bigger than a button for example.
Examples: login, unsaved change feature, form validation, data filtering and querying, data table…
/src/features
├── app-layout/
├── app-root/
└── login/
The pages/
folder contains one sub folder per page of the application and their specific sub components.
/src/pages
├── admin/
├── organisations/
└── user/
The shared/
folder contains small scoped components that is shared across features/
and pages/
.
They are generic elements like buttons, icons, table columns, form elements…
Chekhov’s gun
Let’s see how the codebase of the use case at the beginning would look like:
/src
├── data/
│ └── user/
│ ├── actions.js
│ ├── api.js
│ └── reducer.js
├── features/
├── pages/
│ └── UserProfile/
│ ├── EditUserTab
│ └── EditUserForm
└── shared/
Components and data code is grouped together under the user
namespace, making it easier to identify.
Developer experience first
It can be challenging to know where a file belongs and what makes the cut between features/
or shared/
. What I found worked for me was to plan how I want my code to be used before writing it.
I start with scribbling pseudo JSX code on a piece of paper to decide what the components might look like from the outside: what props it will need, what data I data I need from its parents and what will the output be.
This guides me during the development and helps me write self-contained code with a clear interface.
Finally, do not be afraid to move your code around. A component migh start in shared/
but evolve in a more complex piece of software, making it more of a features/
.
Make it your own
File structure is subjective. You might not like the way I organize my projects and think that it results in too many files or what have you.
And your probably right in some ways. What’s works for me won’t work for everyone. If you’re interested in what I’ve introduced to you but not how I put it in practice that is okay. Find a structure that your team is happy with and stick to it.