Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

Folder Structure

Please note

While this gist has been shared and followed for years, I regret not giving more background. It was originally a gist for the engineering org I was in, not a "general suggestion" for any React app.

Typically I avoid folders altogether. Heck, I even avoid new files. If I can build an app with one 2000 line file I will. New files and folders are a pain.

But when you're in a decently large organization where different development teams work within pretty well-defined feature boundaries of an application, I like the following approach but I would keep the feature folders flat, no route nesting.

Also, don't you dare throw this at your eng team and be like "this is the way". Make up your own minds and don't use me as some weird appeal to authority, I'm just an average dev like anybody else.

Motivations

  • Clear feature ownership
  • Module usage predictibility (refactoring, maintainence, you know what's shared, what's not, prevents accidental regressions, avoids huge directories of not-actually-reusable modules, etc)
  • CI runs only the tests that matter (future)
  • Code splitting (future)

How it works

The file structure maps directly to the route hierarchy, which maps directly to the UI hierarchy.

It's inverted from the model that we've used in other systems. If we consider all folders being either a "generic" or a "feature" folder, we only have one "feature" folder but many "generic" folders.

Examples of "feature" folders:

  • Surveys
  • Admin
  • Users
  • Author

Examples of "generic" folders:

  • components
  • helpers
  • stores
  • actions

Given this route config:

var routes = (
  <Route name="App">
    <Route name="Admin">
      <Route name="Users"/>
      <Route name="Reports"/>
    </Route>
    <Route name="Course">
      <Route name="Assignments"/>
    </Route>
  </Route>
);

We would now set up our directories like this:

app
└── screens
    └── App
        └── screens
            ├── Admin
            │   └── screens
            │       ├── Reports
            │       └── Users
            └── Course
                └── screens
                    └── Assignments

Next, each of these screens has an index.js file, which is the file that handles the entry into the screen, also known as a "Route Handler" in react router. Its very much like a Route in Ember. We'll also have some top-level application bootstrapping stuff at the root, like config/routes.js.

app
├── config
│   └── routes.js
├── screens
│   └── App
│       ├── screens
│       │   ├── Admin
│       │   │   ├── screens
│       │   │   │   ├── Reports
│       │   │   │   │   └── index.js
│       │   │   │   └── Users
│       │   │   │       └── index.js
│       │   │   └── index.js
│       │   └── Course
│       │       ├── screens
│       │       │   └── Assignments
│       │       │       └── index.js
│       │       └── index.js
│       └── index.js
└── index.js

With this structure, each screen has its own directory to hold its modules. In other words, we've introduced "scope" into our application file structure.

Each will probably have a components directory.

app
├── config
│   └── routes.js
├── screens
│   └── App
│       ├── components
│       ├── screens
│       │   ├── Admin
│       │   │   ├── components
│       │   │   ├── screens
│       │   │   │   ├── Reports
│       │   │   │   │   ├── components
│       │   │   │   │   └── index.js
│       │   │   │   └── Users
│       │   │   │       ├── components
│       │   │   │       └── index.js
│       │   │   └── index.js
│       │   └── Course
│       │       ├── components
│       │       ├── screens
│       │       │   └── Assignments
│       │       │       ├── components
│       │       │       └── index.js
│       │       └── index.js
│       └── index.js
└── index.js

These components are used only in the current screen, not even the child screens. So what about when you've got some shared components between screens?

Shared Modules

Every screen also has a "shared" generic directory. If its children share any components with each other or the parent, we put the shared code in "shared". Here is our growing app with some new shared, and not shared modules.

app
├── config
│   └── routes.js
├── screens
│   └── App
│       ├── components
│       ├── screens
│       │   ├── Admin
│       │   │   ├── components
│       │   │   ├── screens
│       │   │   │   ├── Reports
│       │   │   │   │   ├── components
│       │   │   │   │   ├── stores
│       │   │   │   │   │   └── ReportsStore.js
│       │   │   │   │   └── index.js
│       │   │   │   └── Users
│       │   │   │       ├── components
│       │   │   │       └── index.js
│       │   │   ├── shared
│       │   │   │   └── stores
│       │   │   │       ├── AccountStore.js
│       │   │   │       └── UserStore.js
│       │   │   └── index.js
│       │   └── Course
│       │       ├── components
│       │       ├── screens
│       │       │   └── Assignments
│       │       │       ├── components
│       │       │       └── index.js
│       │       └── index.js
│       ├── shared
│       │   └── components
│       │       ├── Avatar.js
│       │       └── Icon.js
│       └── index.js
├── shared
│   └── util
│       └── createStore.js
└── index.js

Note Admin/shared; Reports and Users can both access the shared stores. Additionally, every screen in the app can use Avatar.js and Icon.js.

We put shared components in the nearest shared directory possible and move it toward the root as needed.

Shared module resolution

The way modules in CommonJS are resolved is pretty straightforward in practice: its all relative from the current file.

There is one piece of "magic" in the way modules resolve. When you do a non-relative require like require('moment') the resolver will first try to find it in node_modules/moment. If its not there, it will look in ../node_modules/moment, and on up the tree until it finds it.

We've made it so that shared resolves the same way with webpack modulesDirectories. This way you don't have to require('../../../../../../../../../../shared/Avatar') you can simply do require('components/Avatar') no matter where you are.

Tests

Tests live next to the modules they test. Tests for shared/util/createStore.js live in shared/util/__tests__/createStore.test.js.

Now our app has a bunch of __tests__ directories:

app
├── __tests__
├── config
│   └── routes.js
├── screens
│   └── App
│       ├── components
│       │   ├── __tests__
│       │   │   └── AppView.test.js
│       │   └── AppView.js

... etc.

├── shared
│   └── util
│       ├── __tests__
│       │   └── createStore.test.js
│       └── createStore.js
└── index.js

Why "Screens"?

The other option is "views", which has become a lot like "controller". What does it even mean? Screen seems pretty intuitive to me to mean "a specific screen in the app" and not something that is shared. It has the added benefit that there's no such thing as an "MSC" yet, so the word "screen" causes people to ask "what's a screen?" instead of assuming they know what a "view" is supposed to be.

@felquis
Copy link

felquis commented Aug 16, 2016

@ryanflorence If I have the URL /plans which show all the plans, when I click in one the user goes to /plans/:id

How can I structure it to fit this folder structure?

@kuon
Copy link

kuon commented Aug 22, 2016

I am still struggling a bit with this problem, and I wrote a piece about it: https://the-missing-bit.com/posts/2016/08/21/react-structure/

Copy link

ghost commented Aug 25, 2016

@ryanflorence where do you put not-shared assets, like pictures?

Copy link

ghost commented Aug 28, 2016

babel-plugin-module-resolver will work for tests instead of webpack's modulesDirectories

{
  "plugins": [
    "transform-object-rest-spread",
      ["module-resolver", {
        "root": ["./src/../shared"]
      }]
    ]
}

@emildimitrov
Copy link

emildimitrov commented Sep 1, 2016

Any feedback on using this with react-native project? In my case i have multi-platform codebase like so
src/
common (common for all platforms)
browser-common ( all common things across browsers )
native-common (all common across ios android etc)
browser-mobile ( we have separate website for mobile )
browser-desktop ( separate for desktop )
native-ios ( ios specific )
native-android (android specific)

THanks

@tarun-dugar
Copy link

tarun-dugar commented Dec 17, 2016

@ryanflorence this is awesome! Also, can you tell me how you would you go about integrating redux with this folder structure?

@wuchangming
Copy link

wuchangming commented Jan 18, 2017

@kuon, Awesome!

@cdtinney
Copy link

cdtinney commented Feb 22, 2017

Wanted to comment to say that I am finding this structure very helpful. I was struggling to find a good way to share components.

For anyone else reading that's using Jest to test, all you need to do is add the shared folder to moduleDirectories in your Jest config in package.json in order for the shared components imports to be found when testing.

e.g.

    "moduleDirectories": [
      "node_modules", "shared"
    ]

@RohovDmytro
Copy link

RohovDmytro commented May 14, 2017

Hey, /everyone :) What do you all think about this structure in 2017? What is your practical experience with it?

Would be very thankful if you share.

@rafael-sa
Copy link

rafael-sa commented Jan 8, 2018

Bump.
I have the same doubt as @rogovdm.

@kentcdodds
Copy link

kentcdodds commented Jan 22, 2018

I love it.

@vladfrontend
Copy link

vladfrontend commented Jan 28, 2018

@iammerrick What did you leave this structure in favor of?

@jessevdp
Copy link

jessevdp commented Apr 3, 2018

We've made it so that shared resolves the same way with webpack modulesDirectories. This way you don't have to require('../../../../../../../../../../shared/Avatar') you can simply do require('components/Avatar') no matter where you are.

How did you get this to work?

@jeffersonRibeiro
Copy link

jeffersonRibeiro commented May 10, 2018

@rogovdm @rafael-sa I have the same question. Also does someone have an alternative?

@rchamberlin
Copy link

rchamberlin commented May 17, 2018

@jeffersonRibeiro We have a module called bundle.aliases.js

module.exports = {
  '@components': path.resolve('./app', 'client', 'src', 'components'),
  '@constants': path.resolve('./app', 'client', 'src', 'constants'),
  '@screens': path.resolve('./app', 'client', 'src', 'components', 'screens'),
  '@keyboard': path.resolve('./app', 'client', 'src', 'components', 'keyboard'),
  '@node-modules': path.resolve('./app', 'client', 'node_modules'),
  '@containers': path.resolve('./app', 'client', 'src', 'components', 'containers'),
  '@actions': path.resolve('./app', 'client', 'src', '_actions'),
  '@store': path.resolve('./app', 'client', 'src', '_store'),
  '@reducers': path.resolve('./app', 'client', 'src', '_reducers'),
  '@modules': path.resolve('./app', 'client', 'src', 'modules'),
  '@selectors': path.resolve('./app', 'client', 'src', '_selectors')
}

and then in our webpack config we have:

const alias = require(path.resolve(process.cwd(), 'app', 'config', 'bundle.aliases.js'))
resolve: {
  alias
}

@mikebarnhardt
Copy link

mikebarnhardt commented Jun 19, 2018

I suggest using utils or utilities instead of util to avoid trying to pull from Node's util module when using SSR (such as Razzle).

@JulianSuringa
Copy link

JulianSuringa commented Jul 11, 2018

I think the components is isolated to each component name is not the best option for me its look like a drill components 3 or more layer for nesting is not good for readability must better to keep it simple in the directory and I believe this one https://marmelab.com/blog/2015/12/17/react-directory-structure.html.

@tarang9211
Copy link

tarang9211 commented Jul 15, 2018

Hi guys, is there an example for the folder structure? I am a little confused.

@gnapse
Copy link

gnapse commented Jul 26, 2018

This is great, but unfortunately it would break an editor's ability to track what you're importing and from where. vscode for instance is able to give you useful feedback because it "understands" javascript imports, and gives you auto-complete. Even more so if using TypeScript. Is this configuration something that an editor like that would pick up too?

@coderitual
Copy link

coderitual commented Feb 11, 2019

@ryanflorence

Hey! Are you still using this approach for current projects? It looks nice but I'm wondering if you have applied some modifications maybe.
Cheers!

@yssong119
Copy link

yssong119 commented Apr 3, 2019

It's great. I am applying this approach to my project. Thanks.

@malamoney
Copy link

malamoney commented Jun 19, 2019

So it seems @ryanflorence abandoned this thread a while back. Several people asking to see an example application using the structure, but I have not seen one provided. Would love to see this in action. Anyone still using this approach?

@seongjin605
Copy link

seongjin605 commented Oct 17, 2019

thank you.
i am using nuxt-js.
great structure :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment