Bulletproof React architecture
Source:
bulletproof-react by Alan Alickovic, MIT licensed. The upstream repo is now a monorepo with three app variants:
,
,
. This skill follows the
version as primary. See
NOTICE.md for the license and adaptation note.
Vocabulary
Use these terms exactly. Don't substitute "module," "service," or "component" where the repo means "feature."
- Feature: a self-contained folder under , holding everything specific to one piece of product functionality: its own , , , , .
- Shared code: anything in the top-level , , , , . Usable from any feature or from the app layer. Never depends on a feature.
- App layer: , meaning routes, root component, providers, router config. The only layer allowed to compose multiple features together.
Rules of thumb
A scannable summary. Each one is expanded in the reference files linked below.
| Rule | What it means |
|---|
| No cross-feature imports | importing from is always a bug. Promote to shared code. |
| Code flows one direction | shared → features → app. Never the reverse. Nothing in shared imports from a feature or app. |
| Match state to category first | Component, app, server cache, form, and URL state are five different problems with five different tools. |
Before you apply this
This pays off when the app has multiple features, multiple contributors, or a timeline longer than a few months. It does not pay off on a five-file prototype, a one-off script, or a throwaway component.
Check scale before reaching for structure:
- One feature, a handful of files? Keep flat. The full feature-folder skeleton for one component and one fetch call is the failure mode this skill exists to prevent, not the goal.
- Already have a working pattern in this codebase? Match it. Two competing conventions cost more than either one's imperfections.
- Unsure whether a rule applies? Ask what breaks without it. If nothing breaks, skip it.
The reference files below aren't a checklist. Each solves one problem. Read the one that matches the problem in front of you.
When this skill doesn't apply
Skip it for non-structural work: fixing a bug, writing a single function, styling a component. If the question isn't "where does this go" or "how should this be organized," you don't need it.
This skill assumes you apply judgment about scale. If you catch yourself building every subfolder from the project-structure file just because it's listed, stop and ask whether this feature actually needs all of them.
Gotchas
The mistakes the agent makes most often:
- Using . The upstream repo moved to v7 (library mode). Import from , not .
- Creating a store for state that never leaves one component. That's component state, not application state. Start with . Promote to a store only when another component actually needs the same value.
- Building the full feature skeleton for a small feature. A feature with one component and one fetch call doesn't need , , , , . Just and is fine. You can add folders later when they earn their keep.
- Putting server cache state in Zustand, Redux, or any general-purpose store. Server data belongs in TanStack Query's cache, not in a hand-rolled store. The caching, invalidation, and refetching logic a query client gives you for free is the whole reason it exists.
- Barrel files (index.ts that re-exports everything). They break tree shaking in Vite. Import directly from the file you need.
- Cross-feature imports. Worth repeating: reaching into is never the right call. If two features share logic, promote it to shared code.
- Flagging a single file as over-engineering. One endpoint in is how the API layer starts, not a violation. The over-engineering check targets empty , single-use , and single-type folders, or truly one-off inline fetches outside a feature entirely - not a lone file already living inside a real feature folder.
- Reaching for to transform data or handle user events. If something can be calculated from existing props or state, calculate it during rendering. If logic runs because the user did something, put it in the event handler. Effects are for synchronizing with external systems only. See reference/effects.md.
These resources may help
Each file is short enough to load in full. Read the one relevant to the current task.
-
reference/project-structure.md: app and feature folder layout, the ESLint rules enforcing the import direction above. Start here for a new feature or any "where does this go" question. Most-read file in the skill.
-
reference/api-layer.md: request declaration structure: types, fetcher, hook. Read when writing or reviewing data-fetching code.
-
reference/state-management.md: the five state categories, with a library recommendation for each. Read before adding a
, context, or store. Picking the tool before the category usually means picking wrong.
-
reference/testing.md: test types, where to weight effort, tooling (Vitest, Testing Library, Playwright, MSW).
-
reference/error-handling.md: API error interception, error boundary placement, production error tracking. Short, read in full.
-
reference/security.md: token storage, authorization models (RBAC, PBAC). Read in full, not selectively. Storage and sanitization guidance depend on each other; skipping a section here risks a real vulnerability, not just wasted time.
-
reference/performance.md: code splitting, re-render causes, the
pattern, image and prefetch optimizations. Read when something is measurably slow, not preemptively.
-
reference/overengineering-check.md: a narrow review pass for this skill's own conventions applied where they don't earn their cost. Read when reviewing a codebase for unnecessary structure, or as a self-check after generating a feature scaffold.
-
reference/effects.md: when to avoid Effects and what to do instead. Covers derived state,
, resetting state with keys, chains of computations, fetching with cleanup, and more. Read before reaching for
.
-
reference/quality-check.md: companion tools (react-doctor, Vercel performance rules, composition patterns) that enforce or extend these conventions.