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

*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.**
    

## Issues to be avoided or reduced

* 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.
    

## Expectations

* 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.
    

## Design patterns & concepts

* **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.
    

## Structural principles

* **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](https://notes.dunaevskiy.dev/cheat-sheet-javascript-variable-name-conventions).

## Implementation

Let's assume that we need to create a blog page. We start with one file and extend a structure step by step.

### Pages

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.

```plaintext
./src
└── app
   └── blog
      └── page.tsx
```

URI `http://localhost/blog/article/unique-slug` contains chosen article:

```plaintext
./src
└── app
   └── blog
      ├── articles
      │  └── [slug]
      │     └── page.tsx
      └── page.tsx
```

Pages use default export - Next.js requirement and React Router lazy loading compatibility.

```typescript
// src/app/blog/page.tsx
const BlogPage = () => <></>;

export default BlogPage;
```

### Simple React component

The blog page renders multiple `ArticlePreview` components. An article is rendered by `Article` component.

```plaintext
./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.

```typescript
// src/app/blog/articles/[slug]/_components/Article.tsx
export const Article = () => <></>;
```

### Shared React component

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`).

```plaintext
./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***.

```typescript
// src/app/blog/_components/ArticlePreview.tsx
import { Rating } from './Rating';
export const ArticlePreview = () => <Rating />;
```

```typescript
// src/app/blog/articles/[slug]/_components/Article.tsx
import { Rating } from '@/app/blog/_components/Rating';
export const Article = () => <Rating />;
```

### Complex React component

`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.

```plaintext
./src
└── app
   └── blog
      └── articles
         └── [slug]
            └── _components
               └── Article
                  ├── Article.tsx
                  └── index.ts
```

```typescript
// src/app/blog/articles/[slug]/_components/Article/index.ts
export { Article } from './Article';
```

Interfaces were preserved. Then we add `Article`\-related components.

```plaintext
./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:

```typescript
// 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`.

### Complex React component - Compound Pattern

To prevent exporting useless (on their own) components:

```plaintext
./src
└── app
   └── blog
      └── articles
         └── [slug]
            └── _components
               └── Article
                  ├── Article.tsx
                  ├── ArticleBody.tsx
                  ├── ArticleHeader.tsx
                  └── index.ts
```

```typescript
// 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;
```

```typescript
// src/app/blog/articles/[slug]/page.tsx
import { Article } from './_components/Article';

const Page = () => (
   <Article>
     <Article.Header />
     <Article.Body />
   </Article>
);
export default Page;
```

### Additional secondary items (hooks, validations, etc.)

`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).

```javascript
./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.

```javascript
./src
└── app
   └── blog
      └── articles
         └── [slug]
            └── _components
               └── Article
                  ├── Article.tsx
                  ├── ArticleBody.tsx
                  ├── ArticleHeader
                  │  ├── ArticleHeader.tsx
                  │  ├── hooks
                  │  │  ├── useColoredPart.ts
                  │  │  └── useSomethingSpecial.ts
                  │  └── index.ts
                  └── index.ts
```

### Global components

`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.

```javascript
./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.*

## Conclusion

### Pros

* 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.
    

### Cons

* We continually need to check if all rules are fulfilled, especially the rule about the location of the shared component in the nearest common parent folder.
