Published: May 23, 2022 • 2 min read
You’re creating a React app, and want to organize your components.
Or maybe you’re working in a legacy codebase, with many components in one directory, and you want to better organize them.
The idea: treat components like modules and organize them by route.
So, if you have a dashboard page, the dashboard index and its children are
modules/dashboard/. Shared components live in
Imagine I have two routes,
dashboard/. Here’s my tree:
$ tree src/ ├── App.tsx ├── index.tsx ├── modules │ ├── common │ │ └── components │ │ └── Avatar.tsx │ ├── dashboard │ │ └── components │ │ └── Dashboard.tsx │ ├── login │ │ └── components │ │ └── Login.tsx
Yes, this is more complex than one directory. To wrangle that complexity, when I import a component, I’ll use a full path:
import Avatar from 'modules/common/components/Avatar';
Why full paths? Once I’ve imported something once, I can use whole-line
CTRL-X CTRL-L in Vim, to autocomplete that import into any other
component. It always works, no matter where the component is stored.
The value of this structure appeared to me after continually abandoning two other popular patterns on projects: organization by component, and a flat directory.
With this structure, I have a component called the
Dashboard and so I have a
Dashboard/ with an
index.tsx and all of its children.
In my experience this adds complexity without meaning. Without the routing, it’s hard to infer when a component is presented to a user. Routing makes it about the experience, rather than how one developer chose to implement it.
React’s documentation discourages having any initial opinion about your directory structure: “you’ll likely want to rethink it anyway after you’ve written some real code.”
Hey, I get it. YAGNI (you aren’t going to need it). Respectfully, I disagree. Sometimes, you are going to need it.
Starting every project as though there’s no value to anticipating an architecture feels deliberately naive to me. Most well-factored web apps have a lot in common. Presuming routes and reusable components up front feels safe to me.
Perhaps you don’t like traversing this complex directory structure. To that I’d
say: consider using fuzzy find. If I need to open a footer component on a
dashboard, in an app organized this way and with fuzzy find I can type
dashfoo (“dashboard.*footer”) and most of the time my finder will match
precisely that file. That boring obviousness is the goal.
I learned about this technique a few years ago, in a blog post I can’t find. Please reach out to me if you’re the author and I’ll add an attribution.
In the meantime, it’s important to write my interpretation it in case that post no longer exists. I hope it helps.
✉️ Get better at programming by learning with me. Subscribe to Jake Worth's Newsletter for bi-weekly ideas, creations, and curated resources from across the world of programming. Join me today!
Blog of Jake Worth, software engineer in Maine.