Skip to content
Suleiman Francis

Engineering · Frontend

Designing resilient frontend architecture

Most frontend bugs I’ve fixed trace back to one of three things: unclear ownership of state, validation in the wrong place, or an abstraction that was shared too early. The architecture work is mostly about preventing those.

State has an owner

The first question I ask about any piece of state is who owns it. If the server owns it — a member list, a moderation queue, a feed — it is a cache, and it needs a cache’s tools: deduplication, refetching and invalidation. If the client owns it — a selected tab, a draft, the session — it belongs in client state.

In Whistler Mobile, TanStack Query owns server state and Redux Toolkit holds session and UI state. In Whistler Admin, nearly everything is server state, so the client store shrank to two small Zustand slices: the auth session and the search term. In both, after an action changes data, the affected queries are invalidated rather than patched by hand in several places.

Components are boundaries, not just reuse

A component boundary decides what a piece of UI is allowed to know. Presentational components take data and callbacks. Containers own data fetching and orchestration. That split makes the presentational layer reusable — and makes it obvious where a bug lives.

  • Shared components need a stable, small API. Every prop is a promise to every product that uses it.
  • Duplication is cheaper than the wrong abstraction. I extract a component when the third use appears, not the first.
  • Headless libraries such as TanStack Table keep behaviour shared while markup and accessibility stay under my control.

Validate at the edges

Data should be checked where it crosses a boundary: when a user submits it and when an API returns it. In Whistler Web, nine forms each have a Zod schema that defines the payload, the rules and the messages in one place; Whistler Admin validates its environment configuration with Zod at startup, so a missing variable fails immediately. On Clientshot, data consistency was checked where frontend features met backend services, so a mismatch showed up as an error instead of a quietly wrong dashboard.

Client-side validation is a usability feature. The backend stays the source of truth for anything that matters — permissions, prices, identity.

Design every state, not just the happy path

Loading, empty, error, partial and stale are normal states. On Clientshot, the user list used to flash “no users” before the request had finished; the fix was to tell “still loading” apart from “loaded and empty”, with local skeletons instead of a global loader. In Whistler Web, Playwright runs against a mock API so those states are exercised on every pull request.