The Civic Apps team is a professional services group at Azavea. This means we work on a large number of projects for various clients, and often get the opportunity to reevaluate the technologies we use.
Starting out with a new front-end framework and build tool required researching each project extensively. Given the pace of development of these tools, and their relative newness, it was challenging to find and keep up with the best practices for how to layout your app. Several React/Redux boilerplate projects exist, but we decided to start with a structure similar to how our Backbone apps were organized. This allowed us to get off the ground more quickly than using one of the boilerplate projects since we were more familiar with our own layout. However, we anticipated some pain points since Backbone and React/Redux follow different application paradigms.
We’ve iterated on this structure over the course of four projects, and it’s likely to change in the future, but here is an example of what we have now:
├── common │ ├── components │ │ ├── map │ │ │ ├── index.jsx │ │ │ ├── LegendControl.jsx │ │ │ └── LayerControl.jsx │ │ ├── Tooltip.jsx │ │ └── Modal.jsx │ ├── geocoder │ │ ├── components │ │ │ ├── index.jsx │ │ │ ├── LocationList.jsx │ │ │ └── SearchIndicator.jsx │ │ ├── index.jsx │ │ ├── actions.js │ │ └── reducers.js │ ├── utils.js │ └── settings.js ├── faq │ ├── actions.js │ ├── components │ │ ├── index.jsx │ │ └── Foo.jsx │ ├── index.jsx │ └── reducers.js ├── home │ ├── actions.js │ ├── components │ │ ├── index.jsx │ │ └── Bar.jsx │ ├── index.jsx │ └── reducers.js ├── main.js ├── reducer.js └── store.js
The core idea is to encapsulate project features in separate folders (think of them as an app in Django, for example). While there are some features that can truly be used across projects (almost all of our applications have a geocoder), most of the features will only be used within a given project. However, treating each feature as a reusable module encourages abstraction and encapsulation, making the project more maintainable as a whole.
- Files and folders in the
commonfolder are meant to be shared across the project. We register this folder with Webpack and eslint so that we can do imports from
../../geocoderfrom other locations in the app. No other file can be imported from outside of its folder.
- While each feature has specific actions and reducers, a common reducers and actions file collects them to facilitate shared state and actions across modules.
- We use the AirBnb style guide because it is the most complete and well documented style guide that lined up with our preferences. That’s where we got the idea to name the entry component and container for each module
- In most cases there is only one container for a given feature, but if there are many, we make a
containerssubfolder that stores all of the containers.
- Files at the top-level can import from anywhere.
main.jsis our entry file for the app. The top-level reducer imports all of the other reducers and calls combineReducers.
store.jslives at the top-level because it shouldn’t be imported by any other file. It initializes our Redux store.
- The name for each folder is derived from the purpose of the feature or page. However, if the URL or page name changes, you shouldn’t feel the need to change the name of the folder.
We are pretty happy with where we are now, but we anticipate that we’ll continue to make changes in the future as we run into problems or find areas for improvement.
The following guides were helpful as references. Our thanks goes out to these folks for sharing their work:
Thanks to Matt McFarland, Arianna Robbins, and Terence Tuhinanshu for helping out with this post.