Back to System Design
System Design
medium
mid

How would you structure and optimize a large scale React codebase for scalability and readability?

Feature-folder structure with UI → features → shared → lib dependency direction. Server state in React Query; client state in Zustand/RTK with selectors. Design system with accessible primitives. Per-route code splitting + RSC where applicable. CI gates: types, tests, bundle size, web-vitals, visual regression. ADRs for decisions. Codeowners per feature. Migrate incrementally with codemods.

5 min read·~20 min to think through

Cross-link to [[have-you-used-reactmemo-usecallback-usememo-or-any-other-optimization-technique-]] and [[explain-your-strategy-for-managing-global-state-efficiently-in-a-react-app-with-]].

Structure — feature folders

ts
src/
├ features/
│  ├ cart/
│  │  ├ components/
│  │  ├ hooks/
│  │  ├ api/
│  │  ├ types.ts
│  │  └ index.ts (public surface)
│  ├ checkout/
│  └ profile/
├ shared/
│  ├ ui/         (design system primitives)
│  ├ hooks/
│  └ utils/
├ lib/           (infra: http, storage, auth)
└ app/           (routes)

Dependency direction: app → features → shared → lib. Features never import from each other directly; cross-feature talk goes through events or shared lib.

State

KindTool
ServerReact Query
Local UIuseState
Cross-tree configContext
Cross-tree appZustand / RTK with selectors
URLrouter
FormsRHF + Zod

Design system

Primitives in shared/ui built on Radix / Headless UI. Tokens via CSS variables. Variants via CVA. Storybook with visual regression.

Routing + rendering

  • Per-route code splitting.
  • RSC / SSR / SSG / CSR per route by constraint.
  • Streaming SSR with Suspense for slow data.
  • Prefetch likely-next routes.

Performance budget in CI

  • Bundle size (size-limit) per route.
  • Lighthouse CI for LCP / INP / CLS on key pages.
  • Web Vitals RUM in production.
  • Visual regression for UI primitives.
  • Type tests so type surface doesn't drift.

Multi-team conventions

  • CODEOWNERS per feature folder.
  • RFCs for cross-feature decisions and ADRs for permanent ones.
  • Feature flags for staged rollout.
  • Codemods for breaking changes.
  • Office hours for design system + infra.

Observability

  • Sentry for errors + breadcrumbs.
  • web-vitals → analytics.
  • Tracing for SSR.
  • Action / mutation logs.

Migrations

When a pattern changes (Pages → App Router, Redux → React Query):

  • ADR documenting the choice.
  • Codemod for mechanical migrations.
  • Coexistence period.
  • Deprecation warnings.
  • Final removal in a major version.

Never "freeze old, all-new in feature X" forever.

Tooling

  • TypeScript strict.
  • ESLint + Prettier in CI.
  • Pre-commit hooks for fast feedback.
  • Turbo/Nx if monorepo.

Anti-patterns

  • Type-based folders (components/, hooks/, utils/ at root).
  • Server state in Redux.
  • Cross-feature imports.
  • Memo everywhere reflexively.
  • No ADRs → decisions re-litigated annually.
  • No CI perf gates.

Interview framing

"Feature folders with a strict dependency direction (app → features → shared → lib); no cross-feature imports. State separated by kind: server → React Query, local → useState, cross-tree → Zustand/RTK with selectors, URL state in router, forms with RHF + Zod. Design system primitives in shared/ui built on Radix with CVA variants. Per-route rendering choice (SSR / SSG / RSC / CSR) by constraint. CI gates on types, tests, bundle size, web-vitals, visual regression. CODEOWNERS per feature, RFCs for cross-feature, ADRs for permanent decisions. Codemods for migrations. The meta-rule: scalability is structure + ownership + automation, not 'use the best framework'."

Follow-up questions

  • Walk through one of your ADRs.
  • What does your bundle budget look like?
  • How would you migrate a feature to RSC?

Common mistakes

  • Type-based folders.
  • No CI perf gates.
  • Cross-feature direct imports.
  • No ADR culture.

Performance considerations

  • Structure + CI budgets sustain perf as the team grows; one-shot optimizations regress without budgets.

Edge cases

  • Monorepo with shared design system.
  • Migrating CRA → Next.js mid-scale.
  • Acquired apps coexisting.

Real-world examples

  • Vercel, Linear, Shopify, Atlassian — public-facing scale stories.

Senior engineer discussion

Staff-level signal: structure, ownership, CI gates, ADRs, codemods. Junior signal: 'we use Next.js'.

Related questions