Designing a Resilient Frontend Architecture

September 18, 2024 (1y ago)

Our analytics dashboard began life as a collection of ad‑hoc React components. Dependencies leaked across feature boundaries, CSS collisions were common, and builds regularly shipped regressions. The rewrite forced us to formalise architecture decisions that we now carry into every large project.

Component architecture Image credit: Wikimedia Commons

Domain‑driven modules

We organised the repository by domain rather than by technical layer. Each module contains its components, hooks, routing, and tests. Cross‑module communication happens through typed events, allowing features to evolve independently.

The modules live in a monorepo managed by pnpm workspaces. When a module grows large enough it can be extracted into its own package without changing import paths. This structure keeps package boundaries explicit while avoiding the overhead of full micro‑frontends.

Design system and accessibility baked in

A shared design system provides primitives such as <Button> and <DataTable> built on top of Radix UI and Tailwind. Every component ships with TypeScript props that encode accessibility requirements—if you forget an aria-label, the compiler tells you. Storybook runs axe-core checks on pull requests so violations never reach production.

Data fetching with server components

The move to Next.js 14 let us use React Server Components for data heavy views. Time‑series queries run on the server, stream rendered HTML to the client, and hydrate selectively. We cache stable results in @vercel/edge-config so global readers get sub‑100 ms responses. Client components subscribe to SWR caches to keep charts live without waterfall requests.

Performance budgets

We established a 200 kB gzip budget for the initial bundle. next-bundle-analyzer runs in CI and fails builds that exceed the threshold. Lazy loading, route level code splitting, and prefetching reduced JavaScript shipped to first paint by 60%. WebVitals data flows to Datadog where we graph P95 TTFB and CLS for each release.

Observability and testing

Playwright drives end‑to‑end tests against production builds, capturing traces and screenshots for every step. Runtime errors are surfaced via Sentry with source maps generated during CI. We also instrumented user interactions to emit analytics events that power usability studies.

Operational concerns

Versioned APIs, feature flags, and progressive rollout are handled by LaunchDarkly. Each module can be independently toggled, enabling canary releases. Static generation and edge caching keep cold starts predictable even under deployment surges.

Closing thoughts

A resilient frontend is more than a pretty component library. It is a system of modular boundaries, performance guardrails, and feedback loops that let engineers ship features quickly without fear. Investing in these practices paid off when our team doubled in size yet release cadence stayed constant.