This document captures general architecture and implementation patterns to replicate in a new React application.
- React + TypeScript
- Vite for build/dev server
- React Router
createBrowserRouter(object-based Data Router API) - Zustand for client state
- Service layer for API/backend access
- Vitest + Testing Library for test coverage
Use feature/domain-oriented folders with clear responsibility boundaries:
src/views: route-level screenssrc/components: reusable UI blockssrc/hooks: reusable side-effect and data-loading hookssrc/stores: Zustand stores/slices (state + actions)src/services: backend/data access layer onlysrc/utils: pure utility logic (no I/O side effects)src/types: domain and shared TypeScript typessrc/styles: design tokens, global styles, and theme files
- Use object route config via
createBrowserRouter. - Define a layout route with
element+<Outlet />. - Add
errorElementfor route-level crash handling. - Use wrapper route components for auth/role guards.
- Use route
handlemetadata for page title, breadcrumbs, and header actions. - Keep route declarations centralized in
App.tsx.
- Use Zustand with either:
- one composed store split into logical slices, or
- multiple focused stores (UI, auth, audio/playback, domain data).
- Keep state mutations in store actions, not in components.
- Use selectors (
store(s => s.x)) to minimize rerenders. - Store tracks:
- entity data,
- loading/error state,
- optimistic/pending action flags.
- Create one service module per domain (
XService). - Expose static async methods for use by stores/views.
- Keep backend SDK calls inside services only.
- Add service composition modules for multi-source fetches.
- Prefer server-authoritative operations for critical business writes.
Compose global concerns at app bootstrap:
- Global error boundary
- Auth/session initializer
- Imperative engine/provider wrappers (if needed)
- App root/router provider
This keeps initialization logic outside route screens.
- Views are orchestration layers (load data, wire handlers, render sections).
- Components are focused and mostly presentational.
- Hooks encapsulate reusable async/stateful behavior (loaders, redirects, prefetch).
- Use effect cleanup flags/unsubscribe patterns to avoid stale updates.
- Define shared domain contracts in
src/types. - Type service/store return values explicitly.
- Keep backend payload/DTO shape mapping near the service boundary.
- Avoid
anyin new code unless unavoidable at third-party boundaries.
- Global React error boundary for render/runtime crashes.
- Router
errorElementfor navigation/loader errors. - Service/store try/catch with friendly error mapping.
- Fallback strategies for expected backend/index/query failures.
- Global CSS entry imports token + utility + layout layers.
- Use CSS custom properties (
--token) as design system primitives. - Keep theme overrides in dedicated theme files.
- Use CSS Modules for view-scoped styles.
- Use shared class-based utility styles for common layout patterns.
- Split tests into:
unit: pure utils, stores, components (mock services)integration: backend emulator or test backend environment
- Configure test setup file for DOM/audio/browser mocks.
- Keep integration tests deterministic (sequential when shared state exists).
- Test store actions directly where possible for fast feedback.
- Scripts for
dev,build,lint,test,test:coverage. - Separate integration test config from unit config.
- Use strict TypeScript compile in build pipeline.
- Enforce React hooks and TS lint rules.
- Keep the first paint path intentionally small:
- render a lightweight branded HTML boot shell in
index.html, - avoid mounting non-critical imperative systems at the root if they are not needed for first paint.
- render a lightweight branded HTML boot shell in
- Use lazy route loading for heavier screens and prefetch likely next routes during idle time.
- Split large dependency groups into stable build chunks (for example React/runtime, Firebase/backend SDKs, state libraries).
- Defer secondary systems rather than primary content:
- load dashboard/content first,
- start audio/imperative engines shortly after first paint or on interaction,
- keep dedicated playback routes eager where audio is essential to correctness.
- For dashboard-style pages, prefer sidecar bootstrapping for non-visual systems so visible lists and panels do not remount when a deferred subsystem comes online.
- Gate prefetch work behind simple readiness flags:
- auth settled,
- primary data loaded,
- required engine available (only when truly needed),
- connection is not obviously constrained.
- Scale prefetch intensity by connection quality or
saveData:- fetch fewer candidates on slower links,
- skip aggressive media warmup on constrained connections.
- Use perceived-performance helpers that do not distort layout:
- static boot shell,
- short content fade-in,
- stable placeholders that are replaced once, not repeatedly.
- Avoid mount-time animation resets on live data widgets (for example progress bars) when replacing placeholders; initialize to the current value and animate only subsequent updates.
- When navigating between related views, reuse already-loaded store data before showing loading spinners again.
- Create folder structure above.
- Set up router with layout route, guard wrappers, and route metadata.
- Add
services,stores, andtypesbefore implementing screens. - Add global error boundaries and bootstrap providers in
main.tsx. - Define design tokens and theme files before component styling.
- Add unit/integration test configs early and keep both running in CI.