bulletproof-react Documentation

️ ⚛️ A simple, scalable, and powerful architecture for building production ready React applications.

RAW Doc

File: README.md

Bulletproof React 🛡️ ⚛️




A simple, scalable, and powerful architecture for building production ready React applications.

Introduction

React is an excellent tool for building front-end applications. It has a diverse ecosystem with hundreds of great libraries for literally anything you might need. However, being forced to make so many choices can be overwhelming. It is also very flexible, you can write React applications in any way you like, but that flexibility comes with a cost. Since there is no pre-defined architecture that developers can follow, it often leads to a messy, inconsistent, and over-complicated codebase.

This repo attempts to present a way of creating React applications using some of the best tools in the ecosystem with a good project structure that scales very well. Based on my experience working with a lot of different codebases, this architecture turns out to be the most effective.

The goal here is to serve as a collection of resources and best practices when developing React applications. It is supposed to showcase solving most of the real-world problems of an application in a practical way and help developers write better applications.

Feel free to explore the sample app codebase to get the most value out of the repo.

🤝 Looking for help implementing these patterns at your company? Get in touch

What makes a React application "bulletproof"?

This repo doesn't aim to be a silver bullet for all React applications as there are many different use cases, but it tries to provide a solid foundation for building applications based on the following principles:

  • Easy to get started with
  • Simple to understand and maintain
  • Uses the right tools for the job
  • Clean boundaries between different parts of the application
  • Everyone on the team is on the same page when it comes to how things are done
  • Secure
  • Performant
  • Scalable in terms of codebase and team size
  • Issues detectable as early as possible

Disclaimer:

This is not supposed to be a template, boilerplate or a framework. It is an opinionated guide that shows how to do some things in a certain way. You are not forced to do everything exactly as it is shown here, decide what works best for you and your team and stay consistent with your style.

To get most out of it, do not get limited by the technologies used in this sample app, but rather focus on the principles and the concepts that are being presented here. The tools and libraries used here are just a suggestion, you can always replace them with something that fits your needs better. Sometimes, your project might require a slightly different approach, and that's totally fine.

Table Of Contents:

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. Execute the yarn prepare script.
  4. Make some changes
  5. Test your changes
  6. Push your branch and open a Pull Request

License

MIT


File: apps/react-vite/README.md

React Vite Application

Get Started

Prerequisites:

  • Node 20+
  • Yarn 1.22+

To set up the app execute the following commands.

bash
git clone https://github.com/alan2207/bulletproof-react.git
cd bulletproof-react
cd apps/react-vite
cp .env.example .env
yarn install

`yarn dev`

Runs the app in the development mode.
Open http://localhost:3000 to view it in the browser.

`yarn build`

Builds the app for production to the dist folder.
It correctly bundles React in production mode and optimizes the build for the best performance.

See the section about deployment for more information.


File: apps/nextjs-pages/README.md

Next.js Pages Application

Get Started

Prerequisites:

  • Node 20+
  • Yarn 1.22+

To set up the app execute the following commands.

bash
git clone https://github.com/alan2207/bulletproof-react.git
cd bulletproof-react
cd apps/nextjs-pages
cp .env.example .env
yarn install

`yarn run-mock-server`

Make sure to start the mock server before running the app.
The mock server runs on http://localhost:8080/api.

`yarn dev`

Runs the app in the development mode.
Open http://localhost:3000 to view it in the browser.

Project Structure

Since the pages folder isn't very flexible and doesn't allow file collocation, we are keeping the app folder which is our application layer where we compose all the features, and then we just re-export Next.js page specific files (the pages and getServerSideProps) from the pages folder so Next.js can pick them up and serve as pages.


File: apps/nextjs-app/README.md

Next.js App Application

Get Started

Prerequisites:

  • Node 20+
  • Yarn 1.22+

To set up the app execute the following commands.

bash
git clone https://github.com/alan2207/bulletproof-react.git
cd bulletproof-react
cd apps/nextjs-app
cp .env.example .env
yarn install

`yarn run-mock-server`

Make sure to start the mock server before running the app.
The mock server runs on http://localhost:8080/api.

`yarn dev`

Runs the app in the development mode.
Open http://localhost:3000 to view it in the browser.


File: docs/additional-resources.md

📚 Additional Resources

React

JavaScript

Best Practices


File: docs/api-layer.md

📡 API Layer

Use a Single Instance of the API Client

When your application interacts with either RESTful or GraphQL APIs, it is beneficial to use a single instance of the API client that has been pre-configured and can be reused throughout the application. For example, you can create a single API client instance using the native fetch API or libraries such as axios, graphql-request, or apollo-client with predefined configuration settings.

API Client Example Code

Define and Export Request Declarations

Rather than declaring API requests on the fly, it is recommended to define and export them separately.

Declaring API requests in a structured manner can help maintain a clean and organized codebase as everything is colocated.
Every API request declaration should consist of:

  • Types and validation schemas for the request and response data
  • A fetcher function that calls an endpoint, using the API client instance
  • A hook that consumes the fetcher function that is built on top of libraries such as react-query, swr, apollo-client, urql, etc. to manage the data fetching and caching logic.

This approach simplifies the tracking of defined endpoints available in the application. Additionally, typing the responses and inferring them further down the application enhances application type safety.

API Request Declarations - Query - Example Code
API Request Declarations - Mutation - Example Code


File: docs/application-overview.md

💻 Application Overview

The application is relatively simple. Users can create teams where other users can join, and they start discussions on different topics between each other.

A team is created during the registration if the user didn't choose to join an existing team and the user becomes the admin of it.

Demo

Data model

The application contains the following models:

  • User - can have one of these roles:

    • ADMIN can:
      • create/edit/delete discussions
      • create/delete all comments
      • delete users
      • edit own profile
    • USER - can:
      • edit own profile
      • create/delete own comments
  • Team: represents a team that has 1 admin and many users that can participate in discussions between each other.

  • Discussion: represents discussions created by team members.

  • Comment: represents all the messages in a discussion.

Get Started

To get started, check the README.md file in the application you want to run.


File: docs/components-and-styling.md

🧱 Components And Styling

Components Best Practices

Colocate things as close as possible to where it's being used

Keep components, functions, styles, state, etc. as close as possible to where they are being used. This will not only make your codebase more readable and easier to understand but it will also improve your application performance since it will reduce redundant re-renders on state updates.

Avoid large components with nested rendering functions

Do not add multiple rendering functions inside your application, this gets out of control pretty quickly. What you should do instead is if there is a piece of UI that can be considered as a unit, is to extract it in a separate component.

javascript
// this is very difficult to maintain as soon as the component starts growing
function Component() {
  function renderItems() {
    return ...;
  }
  return {renderItems()};
}

// extract it in a separate component
function Items() {
  return ...;
}

function Component() {
  return (
    
      
    
  );
}

Stay consistent

Keep your code style consistent. For example, if you name your components using pascal case, do it everywhere. Most of code consistency is achieved by using linters and code formatters, so make sure you have them set up in your project.

Limit the number of props a component is accepting as input

If your component is accepting too many props you might consider splitting it into multiple components or use the composition technique via children or slots.

Composition Example Code

Abstract shared components into a component library

For larger projects, it is a good idea to build abstractions around all the shared components. It makes the application more consistent and easier to maintain. Identify repetitions before creating the components to avoid wrong abstractions.

Component Library Example Code

It is a good idea to wrap 3rd party components as well in order to adapt them to the application's needs. It might be easier to make the underlying changes in the future without affecting the application's functionality.

3rd Party Component Example Code

Component libraries

Every project requires some UI components such as modals, tabs, sidebars, menus, etc. Instead of building those from scratch, you might want to use some of the existing, battle-tested component libraries.

These component libraries come with their components fully styled.

  • Chakra UI - great library with great developer experience, allows very fast prototyping with decent design defaults. Plenty of components that are very customizable and flexible with accessibility already configured out of the box.

  • AntD - another great component library that has a lot of different components. Best suitable for creating admin dashboards. However, it might be a bit difficult to change the styles in order to adapt them to a custom design.

  • MUI - the most popular component library for React. Has a lot of different components. Can be used as a styled solution by implementing Material Design or as unstyled headless component library.

  • Mantine - a modern react component library with a lot of components and hooks. It is very customizable and has a lot of features out of the box.

Headless component libraries:

These component libraries come with their components unstyled. If you have a specific design system to implement, it might be easier and better solution to go with headless components that come unstyled than to adapt a fully featured component library such as Material UI to your needs. Some good options are:

Styling Solutions

There are multiple ways to style a react application. Some good options are:

NOTE: Keep React Server Components in mind as they require zero runtime styling solution.

With the rise of headless component libraries, there is another tier of component libraries where predefined components are provided with styling solutions included, but instead of being installed as a package, they are provided as code which can be customized and styled as needed.

Storybook

Storybook is a great tool for developing and testing components in isolation. Think of it as a catalogue of all the components your application is using. Very useful for developing and discoverability of components.

Storybook Story Example Code


File: docs/deployment.md

🌐 Deployment

Deploy and serve your applications and assets over a CDN for best delivery and performance. Good options for that are:


File: docs/error-handling.md

⚠️ Error Handling

API Errors

Implement an interceptor to manage errors effectively. This interceptor can be utilized to trigger notification toasts informing users of errors, log out unauthorized users, or send requests to refresh tokens to maintain secure and seamless application operation.

API Errors Notification Example Code

In App Errors

Utilize error boundaries in React to handle errors within specific parts of your application. Instead of having only one error boundary for the entire app, consider placing multiple error boundaries in different areas. This way, if an error occurs, it can be contained and managed locally without disrupting the entire application's functionality, ensuring a smoother user experience.

Error Boundary Example Code

Error Tracking

You should track any errors that occur in production. Although it's possible to implement your own solution, it is a better idea to use tools like Sentry. It will report any issue that breaks the app. You will also be able to see on which platform, browser, etc. did it occur. Make sure to upload source maps to sentry to see where in your source code did the error happen.