Skip to content
matthewcm.dev

    ↑ ↓ move · ↵ open · tab scopeopen graph →

    Bulletproof React

    Not a boilerplate but a showcase of architecture patterns — organise a React app by feature, keep data access out of components, and wire in testing, mocking and error tracking from day one.

    Growingtended planted 2 min read
    On this page

    Bulletproof React is one of the best free resources on structuring large React applications. The important thing to know is what it isn’t: it’s not a starter kit to clone. It’s a collection of patterns, each of which you can adopt on its own.

    Where I’d already got to — and where it hurt

    Before reading it I’d converged on sensible data layers: API calls → services → transforms into domain models → React Query. That part was clean. The mess was elsewhere: big, complex components and “useSomethingLogic” hooks that became dumping grounds for anything that didn’t fit. Bulletproof React’s answer is mostly about boundaries.

    The patterns worth adopting

    • Feature folders. Group code by feature (features/workflows, features/auth), each with its own components, API hooks, types and tests — instead of global components/, hooks/, utils/ folders that grow forever. When one page hosts several features, treat tabs as nested routes.
    • Layouts per page. A navigation layout plus a content layout, chosen per route, so shared chrome stays mounted and doesn’t re-render between pages.
    • Soft auth in the layout. Check authentication in the layout and redirect, rather than conditionally rendering whole trees, and cache the current user with your data library.
    • Feature-level permissions, not a global RBAC wrapper. A wrapper works for routes but can’t express “you can view this but not edit it”, and it tends to re-render the whole page. Put permission checks next to the thing they protect — and remember the server is the real authority.
    • Start fetching before you render. Route loaders kick off the React Query request before the component mounts, so the data is often cached by the first render.
    • Small stores per feature rather than one global store. Most “global state” is really server cache.
    • Mock the network in development. MSW in the browser, with factory-built fake data, lets you build whole features before the backend exists — see MSW (Mock Service Worker).
    • Name tests by intent: .test.ts for unit, .spec.ts for integration, stories for visual and interaction tests. More in React Testing Strategy.

    Trade-offs

    Feature folders need discipline about what counts as “shared”, or everything drifts back into a common folder. And not every app needs a meta-framework: for an internal tool behind a login, a static single-page app can be cheaper to host and simpler to run than server rendering.

    In practice

    I used these patterns to plan the re-architecture of a large internal React app. One choice I’d repeat: I tried the patterns on a personal project first rather than refactoring the work codebase while I was still learning them. Once they’d proven themselves, the proposal for the team was concrete, with a working example instead of a slide. Related habits: SOLID React and API Modelling.

    • #react
    • #architecture
    • #project-structure
    • #react-query
    • #zustand

    ↪ Links to (4)