The seven frontend architecture decisions you make once
Some decisions can be changed in an afternoon. These seven set the shape of the codebase for years, so they deserve an hour of thought each.
Scalable React architecture comes from three habits: compose components instead of adding boolean props, colocate files by feature rather than by file type, and keep a clear boundary between generic UI primitives and product-specific components. Structure follows change patterns — organise around what gets edited together.
By feature, with a shared layer for things genuinely used across features.
components/
ui/ # primitives: button, input, dialog — no product knowledge
shared/ # cross-feature: page hero, section heading, cards
sections/
home/ # everything the home page renders
pricing/
blog/
lib/
content/ # data + helpers, colocated with their types
api/
constants/The test is simple: when you implement a typical feature request, how many directories do you touch? If a change to the pricing table means editing four unrelated folders, the structure is fighting you.
A component starts with two props. A year later it has nineteen, six of which are booleans that combine into states nobody has tested. This happens because every new requirement was added as configuration rather than composition.
// Configuration: every variation is a new prop
<Card title="…" subtitle="…" showIcon iconName="zap" showFooter
footerText="…" variant="glass" compact isClickable href="/x" />
// Composition: the caller assembles what it needs
<Card href="/x">
<Card.Header icon={<Zap />}>Title</Card.Header>
<Card.Body>Subtitle text</Card.Body>
<Card.Footer>Read the case study</Card.Footer>
</Card>Composition moves the variation to the call site, where it is visible. The component stays small, the combinations stay legal by construction, and nobody has to read the implementation to know what showFooter does when compact is also true.
| UI primitive | Product component | |
|---|---|---|
| Knows about | Styling, accessibility, interaction | Your domain: orders, posts, plans |
| Example | Button, Dialog, Input | PricingTable, OrderRow, ArticleCard |
| Changes when | The design system changes | The product changes |
| Data access | None — props only | May fetch or receive domain objects |
Keep primitives free of domain knowledge. The moment a Button imports your auth context to decide whether to render, it stops being reusable and starts being a liability across every future page.
On the third use, not the first. Premature abstraction produces components with configuration for requirements that never arrived, and those are harder to unwind than duplication. Two similar blocks of JSX are cheaper to maintain than one wrong abstraction.
The exception is size: a component over roughly 200 lines is usually doing several things regardless of reuse, and splitting it improves comprehension even if each piece is used exactly once.
The vocabulary (atoms, molecules, organisms) helps teams agree on granularity, but strict enforcement leads to arguments about whether something is a molecule. A two-layer split — primitives and product components — captures most of the value with less ceremony.
There is no hard rule, but past about 200 lines most components are doing more than one thing. Treat size as a prompt to look, not a rule to enforce.
No. Two or three levels is clear and explicit. It becomes a problem when intermediate components receive props they do not use, which is a signal to compose differently or introduce context.
Only when it has companions — tests, styles, subcomponents. A single-file component in a folder of its own is overhead without benefit.
Harshal Patel
Founder & Lead Engineer, ROVQIX
Harshal leads engineering at ROVQIX, where he has shipped production Next.js, Node.js and PostgreSQL systems for startups, SaaS teams and ecommerce brands. He writes about the trade-offs behind architecture decisions rather than the framework of the week.
ROVQIXdesigns and builds production web platforms — Next.js front ends, Node.js APIs and the infrastructure behind them. Tell us what you're building and we'll scope it with you.
Some decisions can be changed in an afternoon. These seven set the shape of the codebase for years, so they deserve an hour of thought each.
Sprinkling memo and useCallback across a codebase is not optimisation, it is superstition. Measure first, then apply one of four structural fixes.
A test suite nobody trusts is worse than none — it costs time and provides false confidence. Here is what we actually test on client projects.
No spam. Just the occasional case study and craft breakdown.