Guidelines to improve your React folder structure

If you've done any amount of research into the best way to structure your React app's folders, you've probably run into the following advice copy-pasted into forum threads:

Move files around until it feels right.

To me that reads a lot like unhelpfully telling people to RTFM, so let's develop a better answer than that (the quote originated from Dan Abramov in a long-ago deleted Tweet, and has since taken on a life of it's own).

Folder structures for React apps

I'll start by showing you the folder structure I tend to use, then teach you the guiding principles I use for my structure to "feel right" for my purposes.

My Project Structure

src/
|-- components/
| |-- Avatar/
| | |-- Avatar.ts
| | |-- Avatar.test.ts
| |-- Button/
| | |-- Button.ts
| | |-- Button.test.ts
| |-- TextField/
| | |-- TextField.ts
| | |-- TextField.test.ts
|-- contexts/
| |-- UserContext/
| | |-- UserContext.ts
|-- hooks/
| |-- useMediaQuery/
| | |-- useMediaQuery.ts
|-- pages/
| |-- UserProfile/
| | |-- Components/
| | | |-- SomeUserProfileComponent/
| | | | |-- SomeUserProfileComponent.ts
| | | | |-- SomeUserProfileComponent.test.ts
| | |-- UserProfile.ts
| | |-- UserProfile.test.ts
| |-- index.ts
|-- utils/
| |-- some-common-util/
| | |-- index.ts/
| | |-- index.test.ts
|-- App.ts
|-- AuthenticatedApp.ts
|-- index.ts
|-- UnauthenticatedApp.ts

Guiding Principles

1. On testing

Many guides out there will suggest using a __tests__ folder whose structure mimics your app's.

I strongly suggest you avoid this pattern for two reasons:

  1. You'll discourage newcomers from testing by hiding your tests
  2. You'll need to refactor to keep in sync every time you want to move your components/pages around

Instead, use either an index.test.ts file, or a MyComponent.test.ts file. Keep your tests with your components so you remember to test them.

2. Naming your component files

There are two ways you can name your component files:

  1. index.ts

    The advantage of naming your file index.ts is that that you don't repeat yourself - you've already named the folder MyComponent/, why name the file the same?

    The disadvantage is that when you search for index.ts you'll have dozens (if not hundreds, or thousands) of index.ts files, making it hard to find the file you're looking for.

  2. MyComponent.ts

    The advantage of using MyComponent.ts is that VS Code and similar code search tools will find exactly the file you're looking for faster, rather than listing all files in the directory.

It doesn't particularly matter which way you choose to name your components, as long as you pick one, and use it consistently.

3. Separate your pages from your components

React frameworks like Next.js make this easier by using page-based routing, but you can use it in any React project.

For example:

src/
|-- components/
| |-- Avatar/
| | |-- Avatar.ts
| | |-- Avatar.test.ts
| |-- Button/
| | |-- Button.ts
| | |-- Button.test.ts
| |-- TextField/
| | |-- TextField.ts
| | |-- TextField.test.ts
|-- pages/
| |-- UserProfile/
| | |-- Components/
| | | |-- SomeUserProfileComponent/
| | | | |-- SomeUserProfileComponent.ts
| | | | |-- SomeUserProfileComponent.test.ts
| | |-- UserProfile.ts
| | |-- UserProfile.test.ts
| |-- index.ts

In your pages/index.ts file you can then keep your React-Router routes.

4. Keep styled components in your component files

I tend to keep my styled-components in the same file as my components. This is mainly to avoid context switching when trying to figure out what a component does.

You can also choose not to do this, up to you.

In practice, this looks something like this:

//DataChip.js
import React from 'react';
import styled from '@emotion/styled';
const Title = styled.p`
font-size: 16px;
line-height: 16px;
font-weight: 700;
margin-bottom: 4px;
`;
const Value = styled.p`
font-weight: 700;
font-size: 32px;
line-height: 32px;
margin-bottom: 0;
color: ${(props) => {
if (props.sentiment === 'GOOD') return '#30B17E';
if (props.sentiment === 'BAD') return '#E94740';
}};
`;
const ChipHolder = styled.div`
@media (max-width: 600px) {
margin-bottom: 8px;
}
`;
export const DataChip = ({ label, value, sentiment }) => {
return (
<ChipHolder>
<Title>{label}</Title>
<Value sentiment={sentiment}>{value}</Value>
</ChipHolder>
);
};

In the past I would use CSS Modules, and keep the MyComponent.module.css file in the same folder as MyComponent.ts.

5. Split things out only when you need to

If you have a component that only gets imported by one other component, it either belongs in the same file that imports it, or at least in the same folder.

Don't move things up into components/ that aren't shared across multiple components. Also don't move things to a higher level than they get used in general (such as moving a component-specific utility function into the src/ level utils).

We're trying to organise our folders so that when you modify a folder, it has a minimal impact on other folders, and the impact of that change is predictable.

6. [Advanced] Split up your codebase by team

Eventually with a large enough project and engineering team, the CODEOWNERS file just doesn't cut it, and it becomes necessary to split up your codebase by team.

Like all good things in tech, there are trade-offs:

  • On the one hand, your teams stop treading on each other's toes as often (with merge conflicts, and implementing things differently to the team's standard approach)
  • On the other hand, you risk creating silos and having teams re-implement work that another team has done

For example:

src/
|-- growth/
| |-- components/
| | |-- GrowthSpecificAvatar/
| | | |-- GrowthSpecificAvatar.ts
| | | |-- GrowthSpecificAvatar.test.ts
| |-- pages/
| | |-- UserProfile/
| | | |-- Components/
| | | | |-- SomeUserProfileComponent/
| | | | | |-- SomeUserProfileComponent.ts
| | | | | |-- SomeUserProfileComponent.test.ts
| | | |-- UserProfile.ts
| | | |-- UserProfile.test.ts
|-- edgyTeamName/
| |-- components/
| | |-- EdgyTeamNameSpecificAvatar/
| | | |-- EdgyTeamNameSpecificAvatar.ts
| | | |-- EdgyTeamNameSpecificAvatar.test.ts
| |-- pages/
| | |-- UserList/
| | | |-- Components/
| | | | |-- SomeUserListComponent/
| | | | | |-- SomeUserListComponent.ts
| | | | | |-- SomeUserListComponent.test.ts
| | | |-- UserList.ts
| | | |-- UserList.test.ts

At this point you might wonder whether a monorepo would make sense, but that opens up a whole new set of problems to solve, outside of the scope of this article.

(Shameless plug for the useEffect book I wrote below)

Tired of infinite re-renders when using useEffect?

A few years ago when I worked at Atlassian, a useEffect bug I wrote took down part of Jira for roughly one hour.

Knowing thousands of customers can't work because of a bug you wrote is a terrible feeling. To save others from making the same mistakes, I wrote a single resource that answers all of your questions about useEffect, after teaching it here on my blog for the last couple of years. It's packed with examples to get you confident writing and refactoring your useEffect code.

In a single afternoon, you'll learn how to fetch data with useEffect, how to use the dependency array, even how to prevent infinite re-renders with useCallback.

Master useEffect, in a single afternoon.

useEffect By Example's book cover