A good project structure can have a huge impact on how successful a project is in terms of understanding the codebase, flexibility, and maintenance. Projects that are not well structured and maintained can quickly become a big mess and dreadful legacy that no one is too happy to work with.
I will now show you the structure that I very often use in my projects, and explain the reasoning behind it. This structure should be a good starting point for a large-scale application, and you can expand on it depending on the project’s needs. Here is the src structure I can recommend for most projects:
Let’s cover the folders from top to bottom.
tests
First, we have the tests folder, which will contain all the test file of the React application. I use Jest in most of my CRA application. I see that CRA opted for the use of filename.test.js format when writing tests. But this approach is a bit cumbersome on the eyes and brain. You can write tests in a folder named tests and jest will automatically run the tests in that folder.
api
The api folder contains the API Layer of our application. It will have methods that are responsible for performing API requests and communicating with a server.
assets
The assets folder contains fonts, images, and videos. In the fonts, you can keep any custom fonts and typefaces. In images store any pictures used throughout the application.
components
The components directory contains two directories: common and specific. The common directory will contain any reusable components that are commonly used throughout the application. For instance, buttons, form components, components related to typography, and so on. Any components that are not as common would be placed inside of specific components.
Hooks
The hooks directory, as the name suggests, would hold any custom and reusable hooks. Note that any hooks that are not really reusable, but are coupled to a specific feature, should be placed in the same directory as that feature. For instance, imagine we have a newsletter form component that contains a form to sign up a user for a newsletter. This component could utilize a hook called useNewsletterSignup that would handle signing up a user. A hook like this shouldn’t be placed in the global src/hooks directory, but rather locally, as it is coupled to the NewsletterForm component. Here’s what it could look like:
context
The context directory should contain any global-level context state providers.
layout
Layout directory, as the name suggests, should have components that provide different layouts for your pages. For example, if you are building a dashboard application, you could render different layouts depending on if a user is logged in or not.
config
In the config directory, you can put any runtime config files for your application and third-party services. For instance, if you use a service like Firebase or OIDC for authentication, you will need to add configuration files and use them in your app. Just make sure not to confuse config with environmental variables, as anything that goes here will be present in the build bundle.
constants
Here you can put any constant variables that are used throughout the application. It’s a good practice to capitalize your constants to distinguish them from other variables and localized constants in your app.
Below are some examples of defining and using constants:
- Define constants separately
// in constants/appConstants.ts
export const APP_NAME = 'Super app'
export const WIDGETS_LABEL = 'Widgets'
// Somewhere in your app
import { APP_NAME, WIDGETS_LABEL } from '@/constants/appConstants'
console.log(APP_NAME)
// You can also grab all named exports from the file
import * as APP_CONSTANTS from '@/constants/appConstants'
console.log(APP_CONSTANTS.WIDGETS_LABEL)
- Define related constants in one object
// in constants/appConstants.ts
// Create an object with constant values
export const apiStatus = {
IDLE: 'IDLE',
PENDING: 'PENDING',
SUCCESS: 'SUCCESS',
ERROR: 'ERROR'
}
// Somewhere in your app
import { apiStatus } from '@/constants/appConstants'
console.log(apiStatus.PENDING)
helpers
Any utilities and small reusable functions should go here — for example, functions to format date, time, etc.
intl
This directory is optional. Add it if an application requires internalization support. Intl, also known as i18n, is about displaying the content of an app in a format appropriate to the user’s locale. This content can include but not be limited to translated text or specific format of dates, time, and currency. For instance, whilst the UK uses DD/MM/YYYY format, the US uses MM/DD/YYYY.
services
In larger applications, we might have complex business logic code that is used in a few different places. A code like this is a good candidate to be extracted from components and placed somewhere else, and the services folder is a good candidate for that.
store
The store folder is responsible for files related to global state management. There are many state management solutions that can be used for React projects, such as Redux, Zustand, Jotai, and many many more.
styles
You can put global styles, variables, theme styles, and overrides in the styles folder.
types
Here you can put any global and shareable types. With this approach, you can save time and more easily share the TypeScript code and types you and your team need.
views
Usually, the views directory only contains route/page components. For example, if we have a page that is supposed to allow users to view products, we would have a component Products.tsx in the views folder, and the corresponding route could be something like this:
<Route path=”/projects” element={<Products />} />
There is a reason why I said “usually”, though. Many applications have route components in the views, and the rest of the components for it are placed in the components folder. This approach can work for small to medium applications but is much harder to manage and maintain when the number of pages and components grows.
Summary
Choosing the right folder structure is not an easy task. You need to agree with the team on the structure that suits the application, and what might suit one need, might not suit another. Should hopefully be valid for the next years to come. What would you do differently to ensure a good, maintainable structure? Let me know in the comments below.
THANK YOU FOR READING
I hope you found this little article helpful. Please share it with your friends and colleagues. Sharing is caring.
Connect with me on various platforms