React folder structure recommendations (based on Next.js AppRouter)

Search for a command to run...

Recommendations on how to structure TanStack queries and mutations. Provided templates define hooks with extensible interfaces, supporting custom fetch functions per request. RSC/SSR/RCC compatible. Expectations Compatible with some RSC/SSR framewor...

Tool ni is a utility that removes the need to recognize a type of package manager in JS projects (npm, yarn, pnpm, bun). It provides a unified interface and uses appropriate commands instead (calls yarn in yarn projects, npm in npm projects, etc.). L...

Recommendations on how to unify naming convention within a code – formats, acronyms and abbreviations, value-based prefixes, event handlers, etc. Mainly for vanilla TypeScript with a bit of React. Variable name formats It is good to restrict name for...

Various React applications require various structures. Let's define a set of advice on how to think about project organisation and apply it to a medium-sized project.
These recommendations aren't a silver bullet for every project. Apart from the "casual one", which is described below, library-structured monorepo projects, micro frontends, etc., have their own issues and requirements. Each project needs to consider complexity, future development, available budget and other aspects.
Let's discuss a project that could become a medium-sized app with hundreds of components. Consider Next.js, AppRouter, without workspaces and a comfortable budget:
define potential issues to be avoided or reduced,
define axioms, rules and suitable design patterns,
go through different use cases with implementation.
Variables/functions/components are imported from "random" places in the project, which makes it hard to understand which parts of the app may be affected by changes.
An unreasonable number of files in one directory makes the whole project messy.
Exporting everything from everywhere makes it unclear which components can be used without context (e.g. NavItem is dependent on Nav).
Too high or too low granularity in terms of code per file.
A project can contain hundreds of pages, and it is obvious where components are used.
Folder structure may be used with or without Next.js.
Understandable for juniors - a clear structure of folders, files and file naming.
Component-based principle – components emphasise a separation of concerns and distribute themselves as a black-box functionality with the possibility to extend.
Component High cohesion – a component has everything in one place (its file/folder) except shared dependencies or special cases.
Compound pattern – a JSX component that has its own optional structure (e.g. Card, CardBody) and uses a compound structure (e.g. Card, Card.Body) to prevent confusing exports.
Application code related to the functionality is inside ./src folder aliased by @/ path (src root).
_(.*) are special Next.js folders, _ could be avoided without the framework.
@/app is the AppRouter folder containing pages, see Next.js docs.
@/**/* files could be divided into 2 groups - React components and "secondary variables" (= constants, utils, hooks, ...).
ReactComponent is a file or folder having exactly the same interface/behaviour as a file. Both export 1 main component ± secondary variables.
File – ComponentName.tsx contains exactly 1 React component and may have secondary stuff. Everything uses named export. However, secondary variables cannot be used without a relation to the component. Otherwise, see Shared Code and Implementation below.
Folder – ComponentName has a similar interface as a file – exposes 1 React component and secondary variables via ComponentName/index.ts so that it can replace a file ComponentName.tsx without redefining imports across a project. Besides the component itself, inside the folder, we can define other files/folders that are required for its instance. All imports within the folder are relative.
Shared Code – everything that needs to be used in multiple parts of the project.
Component-related – secondary variables can exported from the file/folder with the main component. These variables should be used only with this component. E.g. Nav also provides NavItem, or some special hooks.
Shared between ReactComponents – the shared variable is separated into a stand-alone file, placed into _(.*) folder according to its type and put into the nearest common parent folder. Since separation, all imports of this variable should be absolute @/.
Global modules – some items expected to be used everywhere – typically form fields, buttons, auth hooks, GraphQL Fragments, etc. They are placed in root folders ./src/_(.*)/ according to their type.
The project follows the naming conventions described in the cheat sheet.
Let's assume that we need to create a blog page. We start with one file and extend a structure step by step.
Let's begin with a homepage – a list of articles. URI http://localhost/blog, a page file always has page.tsx filename. We assume that routes are auto-detected by Next.js or manually declared with a router library.
./src
└── app
└── blog
└── page.tsx
URI http://localhost/blog/article/unique-slug contains chosen article:
./src
└── app
└── blog
├── articles
│ └── [slug]
│ └── page.tsx
└── page.tsx
Pages use default export - Next.js requirement and React Router lazy loading compatibility.
// src/app/blog/page.tsx
const BlogPage = () => <></>;
export default BlogPage;
The blog page renders multiple ArticlePreview components. An article is rendered by Article component.
./src
└── app
└── blog
├── _components
│ └── ArticlePreview.tsx
├── articles
│ └── [slug]
│ ├── _components
│ │ └── Article.tsx
│ └── page.tsx
└── page.tsx
All components use named export, which ensures the same component name within an application.
// src/app/blog/articles/[slug]/_components/Article.tsx
export const Article = () => <></>;
We discovered that ArticlePreview and Article need to use the same UI component Rating - let's find the nearest parent folder of both components and create Rating.tsx inside type-related folder (_components).
./src
└── app
└── blog
├── _components
│ ├── ArticlePreview.tsx
│ └── Rating.tsx
├── articles
│ └── [slug]
│ ├── _components
│ │ └── Article.tsx
│ └── page.tsx
└── page.tsx
Rating is an independent component. For ArticlePreview it is a sibling node within /blog/_components folder (main component inside page.tsx) – relative import. For Article it is a component in the upper structure – absolute import.
// src/app/blog/_components/ArticlePreview.tsx
import { Rating } from './Rating';
export const ArticlePreview = () => <Rating />;
// src/app/blog/articles/[slug]/_components/Article.tsx
import { Rating } from '@/app/blog/_components/Rating';
export const Article = () => <Rating />;
Article seems to be too long and needs to be divided into smaller parts – ArticleHeader, ArticleBody. Both of them cannot be used without Article.
At first, we transform the file component into a folder component.
./src
└── app
└── blog
└── articles
└── [slug]
└── _components
└── Article
├── Article.tsx
└── index.ts
// src/app/blog/articles/[slug]/_components/Article/index.ts
export { Article } from './Article';
Interfaces were preserved. Then we add Article-related components.
./src
└── app
└── blog
├── _components
│ ├── ArticlePreview.tsx
│ └── Rating.tsx
├── articles
│ └── [slug]
│ ├── _components
│ │ └── Article
│ │ ├── Article.tsx
│ │ ├── ArticleBody.tsx
│ │ ├── ArticleHeader.tsx
│ │ └── index.ts
│ └── page.tsx
└── page.tsx
Usually, the main component should be the only one exported from the folder. In case that ArticleHeader and ArticleBody need to be used outside Article, we may export them with the Compound Pattern (recommended way) or directly from the Article folder:
// src/app/blog/articles/[slug]/_components/Article/index.ts
export { Article } from './Article';
export { ArticleBody } from './ArticleBody';
export { ArticleHeader } from './ArticleHeader';
In this case, we need to handle them as strictly bounded and not use them without Article or, even worse, inside another React Component as a shared one, as we do with Ratings.
To prevent exporting useless (on their own) components:
./src
└── app
└── blog
└── articles
└── [slug]
└── _components
└── Article
├── Article.tsx
├── ArticleBody.tsx
├── ArticleHeader.tsx
└── index.ts
// src/app/blog/articles/[slug]/_components/Article/Article.tsx
import { Rating } from '@/app/blog/_components/Rating';
import { ArticleBody } from './ArticleBody'
import { ArticleHeader } from './ArticleHeader'
export const Article = () => <Rating />;
Article.Body = ArticleBody;
Article.Header = ArticleHeader;
// src/app/blog/articles/[slug]/page.tsx
import { Article } from './_components/Article';
const Page = () => (
<Article>
<Article.Header />
<Article.Body />
</Article>
);
export default Page;
ArticleHeader needs to have a useColoredPart hook. It isn't shared with Article and belongs only to the ArticleHeader. ArticleHeader stops to be a simple file component and imports the hook with a relative path (we are inside a folder component).
./src
└── app
└── blog
└── articles
└── [slug]
└── _components
└── Article
├── Article.tsx
├── ArticleBody.tsx
├── ArticleHeader
│ ├── ArticleHeader.tsx
│ ├── index.ts
│ └── useColoredPart.ts
└── index.ts
It is possible to create a special folder for same-type components. E.g. /hooks, /validations, /constants, etc.
./src
└── app
└── blog
└── articles
└── [slug]
└── _components
└── Article
├── Article.tsx
├── ArticleBody.tsx
├── ArticleHeader
│ ├── ArticleHeader.tsx
│ ├── hooks
│ │ ├── useColoredPart.ts
│ │ └── useSomethingSpecial.ts
│ └── index.ts
└── index.ts
Article now needs useColoredPart.ts and the hook is expected to be used in many, many others. This type of module could be declared as a global one. The same logic has, e.g., UI JSX components. We move them to the root.
./src
├── _components
│ ├── Button.tsx
│ └── FormFieldText.tsx
├── _hooks
│ └── useColoredPart.ts
└── app
└── blog
├── _components
│ ├── ArticlePreview.tsx
│ └── Rating.tsx
├── articles
│ └── [slug]
│ ├── _components
│ │ └── Article
│ │ ├── Article.tsx
│ │ ├── ArticleBody.tsx
│ │ ├── ArticleHeader
│ │ │ ├── ArticleHeader.tsx
│ │ │ ├── index.ts
│ │ │ └── useSomethingSpecial.ts
│ │ └── index.ts
│ └── page.tsx
└── page.tsx
These components theoretically may be used across projects. Absolute imports inside nested folders, relative within the same level.
A similar concept is used, e.g., by the npm package manager, which places installed dependencies into a file system tree. The only difference is that npm considers all dependencies root-first, not leaf-first.
It is evident which part of an application we can affect after changes in any file.
Rules for imports reduce the number of required import changes across an app in cases of moving or adding complexity to a component.