When building small hobby projects or trying a new concept or library, developers can start adding files to a folder without a plan or organizing structure. These could include CSS, helper components, images, and pages. A single folder for all resources becomes unmanageable as the project grows. Any respectably sized codebase should be organized into an application folder structure based on logical criteria. The decision on how to structure your files and application components could be a personal/team choice. It would also generally depend on the application domain and technology used.
This chapter mainly focuses on folder structures for React.js applications that could help in better management of our projects as they grow.
React.js itself does not provide a guideline on structuring projects but does suggest a few commonly used approaches. Let’s look at these and understand their pros and cons before discussing folder structures for projects with added complexities and Next.js applications.
At the high level, you can group files in a React application in two ways:
Create folders for every application module, feature, or route.
Create folders for different types of files.
Let us look at this classification in detail.
In this case, the file structure would mirror the business model or the application flow. For example, if you have an ecommerce application, you will have folders for product, productlist, checkout, etc. The CSS, JSX components, tests, subcomponents, or helper libraries explicitly required for the product module reside in the product folder:
common/ Avatar.js Avatar.css ErrorUtils.js ErrorUtils.test.js product/ index.js product.css price.js product.test.js checkout/ index.js checkout.css checkout.test.js
The advantage of grouping files by feature is that if there is a change to the module, all the affected files are colocated in the same folder, and the change gets localized to a specific part of the code.
The disadvantage is common components, logic, or styles used across modules should be identified periodically to avoid repetition and promote consistency and reuse.
In this type of grouping, you would create different folders for CSS, components, test files, images, libraries, etc. Thus, logically related files would reside in different folders based on the file type:
css/ global.css checkout.css product.css lib/ date.js currency.js gtm.js pages/ product.js productlist.js checkout.js
The advantages of this approach are:
You have a standard structure that you can reuse across projects.
Newer team members with little knowledge of the application-specific logic can still find files for something like styles or tests.
Common components (such as date pickers) and styles imported in different routes or modules can be changed once to ensure that the effect is seen across the application.
The disadvantages are:
A change in logic for a specific module would likely require changes in files across different folders.
As the number of features in the application grows, the number of files in different folders would increase, making it difficult to find a specific file.
Either of these approaches could be easy to set up for small- to mid-sized applications with a small number of files (50 to 100) per folder. For larger projects, however, you may want to go for a hybrid approach based on the logical structure of your application. Let us look at some of the possibilities.
Here you would group all common components required across the application in a Components folder and all application flow-specific routes or features in a domain folder (the name could be domain, pages, or routes). Every folder can have subfolders for specific components and related files:
css/ global.css components/ User/ profile.js profile.test.js avatar.js date.js currency.js gtm.js errorUtils.js domain/ product/ product.js product.css product.test.js checkout/ checkout.js checkout.css checkout.test.js
Thus, you can combine the advantages of both “Group by file type” and “Group by feature” by colocating related files, which change together frequently and common reusable components and styles used across the application.
Depending on the complexity of the application, you can modify this to a flatter structure without subfolders or a more nested structure:
The following example illustrates a flatter structure:
domain/ product.js product.css product.test.js checkout.js checkout.css checkout.test.js
The following example shows a more nested structure:
domain/ product/ productType/ features.js features.css size.js price/ listprice.js discount.js
It’s best to avoid deep nesting with more than three to four levels because it becomes harder to write relative imports between folders or update those imports when the files are moved.
A variation to this approach is to create folders based on views or routes, in addition to those based on domain, as discussed here. A routing component can then coordinate the view to be displayed based on the current route. Next.js uses a similar structure.
Modern React apps use different features such as Redux, stateful containers, Hooks, and Styled Components. Let’s see where the code related to these would fit in the application structure proposed in the previous section.
Redux documentation strongly recommends colocating logic for a given feature in one place. Within a given feature folder, the Redux logic for that feature should be written as a single “slice” file, preferably using the Redux Toolkit createSlice
API. The file bundles {actionTypes, actions, reducer
} to a self-contained, isolated module. This is also known as the “ducks” pattern (from Redux). For example, as given here:
/src index.tsx: Entry point file that renders the React component tree /app store.ts: store setup rootReducer.ts: root reducer (optional) App.tsx: root React component /common: hooks, generic components, utils, etc /features: contains all "feature folders" /todos: a single feature folder todosSlice.ts: Redux reducer logic and associated actions Todos.tsx: a React component
Another comprehensive example that uses Redux without creating containers or Hooks is available here.
If you have structured your code to categorize components into presentational components and stateful container components, you can create a separate folder for the container components. Containers let you separate complex stateful logic from other aspects of the component:
/src /components /component1 index.js styled.js /containers /container1
You can find a complete structure for an app with containers in the same article.
Hooks can fit into the hybrid structure just like any other type of code. You can have a folder at the app level for common Hooks that can be consumed by all React components. React Hooks used by only one component should remain in the component’s file or a separate hooks.js file in the component’s folder. You can find a sample structure here:
/components /productList index.js test.js style.css hooks.js /hooks /useClickOutside index.js /useData index.js
If you are using Styled Components instead of CSS, you can have style.js files instead of the component-level CSS files mentioned earlier. For example, if you have a
titlebar
component, the structure would be something like this:
/src/components/button/ index.js style.js
An application-level theme.js file would contain the values for colors to be used for background and text. A globals component could include definitions for common style elements that other components can use.
In addition to folder structure, some other best practices that you can consider when structuring your React applications are as follows:
Use import aliasing to help with long relative paths for common imports. This can be done using both Babel and webpack configurations.
Wrap third-party libraries with your API so that they can be swapped if required.
Use PropTypes with components to ensure type checking for property values.
Build performance depends on the number of files and dependencies. If you’re using a bundler such as webpack, a few suggestions for improving build times may be helpful.
When using a loader, apply it to only those modules that need to be transformed by it. For example:
const
path
=
require
(
'path'
);
module
.
exports
=
{
//...
module
:
{
rules
:
[
{
test
:
/\.js$/
,
include
:
path
.
resolve
(
__dirname
,
'src'
),
loader
:
'babel-loader'
,
},
],
},
};
If you’re using hybrid/nested folder structures, the following example from webpack shows how to include and load files from different paths in the structure:
const
path
=
require
(
'path'
);
module
.
exports
=
{
//...
module
:
{
rules
:
[
{
test
:
/\.css$/
,
include
:
[
// Include paths relative to the current directory starting with
// `app/styles` e.g. `app/styles.css`, `app/styles/styles.css`,
// `app/stylesheet.css`
path
.
resolve
(
__dirname
,
'app/styles'
),
// add an extra slash to only include the content of the directory
// `vendor/styles/`
path
.
join
(
__dirname
,
'vendor/styles/'
),
],
},
],
},
};
Files that do not have an import
, require
, define
, etc. to reference other modules need not be parsed for dependencies. You can avoid parsing them using the noParse
option.
Next.js is a production-ready framework for scalable React apps. While you can use the hybrid structures, all the routes in the app must be grouped under the pages folder. (URL of the page = the root URL + relative path in the pages folder).
Extending the structures discussed previously, you can have folders for common components, styles, Hooks, and utility functions. Code related on account of the domain can be structured into functional components that different routes can use. Finally, you will have the pages folder for all routes. Here’s an example for this based on this guide:
--- public/ Favicon.ico images/ --- common/ components/ datePicker/ index.js style.js hooks/ utils/ styles/ --- modules/ auth/ auth.js auth.test.js product/ product.js product.test.js --- pages/ _app.js _document.js index.js /products [id].js
Next.js also provides examples for many different types of apps. You can bootstrap these using create-next-app
to create the template folder structure provided by Next.js. For example, to create the template for a basic blog app, use:
yarn create next-app --example blog my-blog
This chapter discussed multiple different options for structuring React projects. Depending on the size, type, and components used in the project, you can choose the one most relevant to yours. Sticking to a defined pattern for structuring a project would help you explain it to other team members and prevent projects from getting disorganized and unnecessarily complicated.
The next chapter is the concluding chapter of this book and provides additional links that might be helpful when learning about JavaScript design patterns.