All articles
EngineeringReactTypescriptArchitecture

Component architecture that actually scales

Learn how to structure scalable React codebases using a 3-layer component model. Eliminate code duplication, improve maintainability, and clean up components.

Nguyen Bao Huy 3 min read
Abstract red and white geometric shapes representing layered frontend component architecture

Every codebase I have inherited had the same disease: a components folder with 180 files, no ownership, and three slightly different buttons. The fix is not a new framework. It's agreeing on boundaries before the second developer joins.

Three layers, no exceptions

I split the UI into primitives, composites, and features. Primitives know nothing about the product. Composites know about a domain. Features know about a route.

  • Primitives — buttons, inputs, cards. Zero data fetching, zero domain words.
  • Composites — ProjectCard, BlogCard. They accept domain types as props.
  • Features — full sections wired to loaders and server functions.

Data flows down, intent flows up

A composite should never fetch. If a card needs data, the feature above it fetches and passes it in. This single rule makes 90% of your components trivially testable and instantly reusable.

typescript
// feature layer — owns the data
export function WorkSection() {
  const { data: projects } = useSuspenseQuery(projectsQuery);
  return (
    <Grid>
      {projects.map((p) => (
        <ProjectCard key={p.slug} project={p} />
      ))}
    </Grid>
  );
}

// composite layer — owns the presentation
export function ProjectCard({ project }: { project: Project }) {
  return <article className="rounded-2xl">{project.title}</article>;
}

If you cannot describe what a component knows in one sentence, it knows too much.

Naming is the cheapest documentation

Name files after what they render, not how they render it. pricing-table.tsx survives a redesign; three-column-grid.tsx does not.

Share articleTwitter / XLinkedIn