/react-redux-boilerplate

A meticulously crafted, extensible, and robust architecture for constructing production-grade React applications 🚀

Primary LanguageTypeScriptMIT LicenseMIT

React Redux Boilerplate

MIT License GitHub Actions Workflow Status

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:

  • A well-defined folder structure and code organization for enhanced maintainability and scalability, with particular attention to the possibility of splitting and sharing components across projects.
  • A robust state management approach to effectively manage data and maintain application code SOLID
  • An automated release system to streamline the deployment process and ensure seamless updates with automatic changelog, version bump and tags
  • Consistent code formatting and styling to enhance code readability, maintain consistency, and promote adherence to best practices
  • A headless theme, with few dependencies and focus on accessibility
  • Powerful E2E tests with Cucumber and Playwright

⚡ Features

  • Blazing fast build system: Vite + React SWC + Yarn 4
  • App State: Redux Toolkit 2
  • Theme: Radix + Shadcn + Tailwind
  • Format and Style: Eslint + Prettier
  • Release flow: Husky + Commitlint + Semantic-release
  • Mocked server for fast development: MSW
  • E2E testing: Cucumber + Playwright

👉 Table of Contents

🗄️ Folder Structure and Code Organization

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:

  1. Reduced Coupling: By isolating each feature within its own slice, dependencies between different parts of the codebase are minimized. This foster improved code comprehension, facilitates code modifications, and mitigates the risk of introducing bugs during changes.
  2. Enhanced Maintainability: by simplifying the process of locating code pertaining to specific features. This stems from the organization of feature-specific code within a single slice, rather than scattering it across multiple layers or components.
  3. Accelerated Development: by enabling parallel work on different features. Each feature can be developed and tested independently, fostering a more streamlined development process.
  4. Streamlined Testing: Testing becomes more manageable due to the ability to isolate each feature for testing purposes.
  5. Improved Onboarding: facilitates a smoother onboarding experience for new developers. The organization of code around user features, rather than technical layers or components, aligns with developers' familiarity.
  6. Packetization: Features can be effortlessly moved and shared across projects.

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 configuration
|
+-- features                  # features used across the entire application
|
+----- Feature X              # Just a folder container of a group of features
|
+---------- Feature A
|
+---------------- store       # redux slice
|
+---------------- hooks       # react hooks
|
+---------------- components  # react components
|
+---------------- services    # services consumes by redux
|
+---------- Feature B         # (slices, hooks, etc. inside)
|
+---------- Feature C         # (slices, hooks, etc. inside)
|
+-- core
|
+----- config        #  all the global configuration, env variables etc. get exported from here and used in the app
|
+----- helpers       #  any helper function that don't belong to a feature i.e. logging, generic storage (localstorage), etc.
|
+----- store         #  redux configuration
|
+-- UI               # UI components and configuration
|
+----- Elements      # Basic and complex UI elements
|
+----- Layout        # Page layouts used across the app

FAQ

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.

🗃️ State management: Why redux?

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:

  • Components: Solely responsible for dispatching actions and displaying data through selectors. No business or domain logic inside.
  • Selectors and Reducers: Encapsulate the application's business and domain logic. Their pure function nature renders them highly testable, reusable through composition, and exceptionally maintainable.
  • RTK Query, thunks and Listener middleware: Orchestrates the management of all side effects.

Redux flow

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.

🧱 UI Components and Style system

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.

Why Radix + Shadcn as UI component library?

Why Tailwind?

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

🌐 Release system

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:

  • Real-time environment updates with every commit: Implement a job that triggers releases on the "environment" branch (e.g., develop).
  • Multiple environments: Duplicate the deployment logic for additional environments or centralize deployment using a baseline repository. Alternatively, leverage semantic-release's capability to generate context-specific tags (e.g., beta/alpha).

The complete toolkit:

  • Commitlint: Enhances commit consistency for automated versioning and changelog generation
  • Husky: Mandates commitlint execution for every commit
  • Semantic-release: Automates release/tag/changelog creation within the pipeline

This comprehensive toolkit streamlines the release process, ensuring efficiency, consistency, and reproducibility.

Why is important to have standard commits?

  1. Automated Changelog Generation and Semantic Versioning: Standardized commits enable the seamless generation of comprehensive changelogs and facilitate the accurate determination of semantic version increments.
  2. Enhanced Change Identification: By employing fundamental keywords such as "feat," "chore," and "revert," teams can effortlessly discern the nature of the modifications, fostering clarity and collaboration.
  3. Streamlined Onboarding for New Contributors: Standardized commits significantly reduce the onboarding effort for developers, enabling them to swiftly integrate into the team and contribute meaningfully.

👁️ Format and style

TLDR; Embrace a consistent style guide, avoid Eslint's flat format, and leverage Prettier for formatting

  • Postpone utilizing the new flat format until these issues are resolved: eslint and eslint-typescript
  • Forgo the use of eslint-plugin-prettier opt for eslint-config-prettier. In general, delegate formatting responsibilities to Prettier, not Eslint as they also suggest in their official docs.
  • Consider the available style guide options: standard, airbnb, google. For a comprehensive comparison, you can check here. Our preference is the airbnb style guide due to its widespread adoption and comprehensive coverage of React-specific guidelines.

List of rules:

⚠️ Error Handling and Analytics

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:

Sentry redux actions

Additionally, we can easily track and potentially rehydrate the user's state at the precise moment of the error (screenshot from sentry):

Sentry redux state

📚 Additional libraries

  • Time and dates: date-fns - Moment is dead. Date-fns is a maintained, fast, functional and modular alternative.
  • Forms: react-hook-form - Small size with no dependencies, good performance and DX and UX experience.
  • Data manipulation: ramda - Alternative for lodash that promotes functional programming
  • Logging and monitoring: Sentry

🏭 Run production locally

npm install --global serve
yarn build
cd dist
serve -s

Contributing

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:

  1. Clone this repo
  2. Create a branch: git checkout -b your-feature
  3. Make some changes
  4. Test your changes
  5. Push your branch and open a Pull Request

License

MIT