A meticulously crafted, extensible, and robust architecture for constructing production-grade React applications 🚀
MIT License
A meticulously crafted, extensible, and robust architecture for constructing production-grade React applications. The project aim to provide guidelines on the development key points of a long term React project:
npx degit marcoturi/react-redux-boilerplate my-app
cd my-app
# To enable yarn 4 follow the instruction here: https://yarnpkg.com/getting-started/install
yarn #Install dependencies.
yarn create:env #Create a .env file
yarn start
- start a development server with hot reload.yarn build
- build for production. The generated files will be on the dist
folder.yarn preview
- locally preview the production build.yarn test
- run unit and integration tests.yarn test:coverage
- run unit and integration tests with coverage.yarn e2e:local
- run E2E test locally. Make sure to run yarn start before in a separate shell.yarn type-check
- check for typescript errors.yarn outdated
- update dependencies interactively.yarn format
- format all files with Prettier.yarn lint
- runs ESLint.yarn create:env
- creates default envs.TLDR; Embrace the vertical slice architecture
The vertical slice architecture is the recommended structure. Each feature encapsulates components, state management (redux), API interactions, and hooks. This architecture offers several compelling advantages:
Over the years, different structures were born based on different layers of features, including Atomic design or Feature slice. However, dividing code into numerous layers of features reduce the developer experience by the constant navigation between multiple folders. Also, the moment you want to move the logic to another package the refactor is also more invasive.
If you need to re-use features across projects, within the following structure is very easy to move the folders in a monorepo package without much re-factoring (thanks also to the usage of alias in imports).
.
└── src/
├── assets → Assets folder can contain all the static files such as images, fonts, etc.
├── pages → Routes and pages
├── shared/
│ ├── config → All the global configuration, env variables etc. get exported from here and used in the app
│ ├── helpers → Any helper function that do not belong to a feature i.e. logging, generic storage (localstorage), etc.
│ └── store → Redux configuration
├── UI/
│ ├── elements → Basic and complex UI elements
│ └── layout → Page layouts used across the app
└── features/ → Features used across the entire application
└── Feature X/ → Optional: a folder container for a group of features
├── Feature A/
│ ├── store → Redux slice
│ ├── hooks → React hooks
│ ├── components → React components
│ └── services → Services consumed by redux
├── Feature B
└── Feature C
Q: What to do if features folder start multiplying ? A: Try to avoid more than 6 folders in the same folder, group them inside "scope" folders.
Q: I have only a redux slice, where should I put it? A: Put it in the features folder. You don't know if you will have to create components around it in the future.
TLDR; Embrace Redux for keep changes in your app more predictable and traceable.
Why should Redux reign supreme over the multitude of state management solutions? His strength lies in enforcing codebase consistency and facilitating effortless debugging through the ability to visualize, store, and potentially rehydrate application state in the event of errors (see error section).
Within a Redux-powered application, responsibilities are meticulously defined:
While some may argue that newer state management solutions offer less boilerplate, these often lack a designated location for business code placement. In the React ecosystem, custom hooks provide the cleanest approach for addressing this issue. However, the reliance on custom hooks to encapsulate domain logic in a large team, can quickly lead to an unwieldy codebase, with components ballooning to over 200-300 lines. In my experience, without a clear project-defined location for application domain logic, it inevitably gravitates towards react/ui components, rendering them unmaintainable.
TLDR; Chose UI Components with few dependencies
Choosing a UI library can be a complex decision, and it is often influenced by both the requirements of the project and the capabilities of the team. To ensure that a project is long-lived and maintainable, I recommend choosing a UI library that does not tie you with many dependencies and exposing the APIs of UI components to a minimum by encapsulating them.
Consistency and Maintainability: Tailwind's utility-first approach promotes consistent styling across the entire codebase. Developers can easily reuse predefined classes and components, ensuring a unified look and feel throughout the project. This consistency makes it easier for new team members to onboard and maintain the codebase, reducing the risk of inconsistencies and maintainability issues.
Rapid Prototyping and Development: Tailwind's declarative syntax allows developers to quickly prototype and develop features without the overhead of writing complex CSS rules. The prebuilt utility classes provide a quick and straightforward way to style elements, accelerating the development process and enabling developers to focus on functionality rather than styling intricacies.
Reduced Code Bloat and Complexity: Tailwind eliminates the need for writing repetitive CSS rules, which can often lead to code bloat and complexity. The utility-first approach encourages developers to utilize predefined classes, reducing the amount of code they need to write and maintain. This simplification enhances code readability, maintainability, and overall project health.
Collaboration and Efficiency: Tailwind's consistency and component-based approach facilitate efficient collaboration among team members. Developers can easily share and reuse styled components, ensuring consistency and reducing duplication of effort. This collaboration promotes efficiency and productivity, particularly in large teams where multiple developers are working on the same codebase.
Responsive Design and Accessibility: Tailwind CSS provides a comprehensive set of utility classes for responsive design, enabling developers to easily create responsive layouts that adapt to different screen sizes and devices. Additionally, Tailwind's accessibility features make it easier to build websites that are inclusive and usable by people with disabilities.
Modular and Customizable: Tailwind's utility classes can be organized into custom components and modules, allowing developers to tailor the framework to the specific needs of their project. This modularity provides flexibility and customization, ensuring that Tailwind fits seamlessly into the project's architecture and design principles.
In summary, Tailwind CSS offers a plethora of benefits for long-term projects and large teams, including consistency, maintainability, rapid prototyping, reduced code bloat, collaboration efficiency, responsive design, accessibility, modularity, and a great developer experience. Its utility-first approach, prebuilt components, and focus on code quality make it an excellent choice for building complex and maintainable web applications.
TLDR; Automate Versioning and Changelog Generation via a standalone Pipeline
Over the years there have been different release systems: git flow, github flow, gitlab flow and truck-based delivery.
Beyond the choice of the release system, this project suggests automating this process within the pipeline, in order to avoid discrepancies and inefficiencies with semantic-release.
Streamlined Release Process
To initiate a new release, developers simply need to merge a branch into main. The system seamlessly handles versioning and changelog generation based on commit history. Naturally, this process is contingent upon successful test and build executions.
Adaptability to diverse Environments
This method seamlessly adapts to various deployment scenarios:
The complete toolkit:
This comprehensive toolkit streamlines the release process, ensuring efficiency, consistency, and reproducibility.
Why is important to have standard commits?
TLDR; Embrace a consistent style guide, avoid Eslint's flat format, and leverage Prettier for formatting
List of rules:
TLDR; If you use redux correctly, you achieve exceptional developer experience during debugging.
One of the compelling advantages of the architecture presented in this project is its remarkable ability to facilitate debugging and error handling, fostering an exceptional developer experience.
Throughout the development process, the Redux Dev Tools extension for the browser provides real-time insights into the application's state transitions triggered by user interactions. In a production environment, when utilized appropriately (i.e., components dispatch Redux actions without encapsulating any business logic within themselves), we gain the capability to meticulously trace every user action preceding the occurrence of an error or in case we want to track them for analytics purposes:
Additionally, we can easily track and potentially rehydrate the user's state at the precise moment of the error (screenshot from sentry):
Contributions are always welcome! If you have any ideas, suggestions, fixes, feel free to contribute. You can do that by going through the following steps:
git checkout -b your-feature