-
Notifications
You must be signed in to change notification settings - Fork 0
004 react typescript vite
Date: 2026-01-05
Status: Accepted
Deciders: Frontend Team
OpenCloudTouch frontend needs a modern, performant UI framework for controlling Bose SoundTouch devices. Requirements:
- Type Safety: Catch errors at compile-time, not runtime.
- Fast Development: Hot module reload (HMR) for rapid iteration.
- Component Reusability: Build reusable UI components (PresetButton, DeviceCard).
- Mobile Support: Responsive design for tablets/phones.
- Modern Standards: Use latest JavaScript features (async/await, ESM).
We will use React 19 + TypeScript 5 + Vite 4 for the frontend.
| Technology | Version | Purpose |
|---|---|---|
| React | 19.0.0 | UI framework (functional components, hooks) |
| TypeScript | 5.9.3 | Type safety, autocompletion |
| Vite | 4.x | Build tool, dev server (fast HMR) |
| React Query | 5.x | Server state management, caching |
| React Router | 7.x | Client-side routing |
| Vitest | 4.x | Unit testing framework |
| Cypress | 15.x | E2E testing |
Component Hierarchy:
App.tsx
├── ErrorBoundary.tsx
├── Routes
│ ├── Dashboard.tsx
│ │ ├── DeviceCard.tsx
│ │ │ └── PresetButton.tsx
│ │ └── RadioSearch.tsx
│ ├── Settings.tsx
│ └── Welcome.tsx
└── Providers (QueryClient, Router)
State Management:
// ✅ Server state: React Query
const { data: devices } = useQuery({
queryKey: ['devices'],
queryFn: fetchDevices,
refetchInterval: 5000, // Poll every 5s
});
// ✅ UI state: useState
const [volume, setVolume] = useState(50);
// ✅ Form state: Controlled components
<input value={name} onChange={(e) => setName(e.target.value)} />API Communication:
// api/devices.ts
export async function fetchDevices(): Promise<DevicesResponse> {
const response = await fetch(`${API_URL}/api/devices`);
if (!response.ok) throw new Error('Failed to fetch devices');
return response.json();
}
// Hook usage
const { data, error, isLoading } = useQuery({
queryKey: ['devices'],
queryFn: fetchDevices,
});- Type Safety: TypeScript catches 80%+ of bugs before runtime.
- Fast HMR: Vite rebuilds in <100ms, React HMR preserves state.
- Modern React: Hooks/functional components (no class components).
- Developer Experience: Autocompletion, refactoring, inline docs.
- Performance: Vite uses ESM natively, no bundling in dev mode.
- Testing: Vitest is Vite-native, fast test execution.
- Bundle Size: React 19 is 50KB gzipped (acceptable for local dashboard).
- Learning Curve: TypeScript requires understanding generics, interfaces.
- Build Complexity: Vite config needed for Cypress, proxies, etc.
// ✅ GOOD: Functional component
export function DeviceCard({ device }: { device: Device }) {
const [volume, setVolume] = useState(device.volume);
return <div>{device.name}</div>;
}
// ❌ BAD: Class component (legacy pattern)
class DeviceCard extends React.Component { ... }interface DeviceCardProps {
device: Device;
onPresetClick?: (presetNumber: number) => void;
}
export function DeviceCard({ device, onPresetClick }: DeviceCardProps) {
// Autocomplete + type checking
}// ✅ GOOD: Declarative, handles loading/error/caching
const { data, isLoading, error } = useQuery({
queryKey: ['devices'],
queryFn: fetchDevices,
});
// ❌ BAD: Manual fetch + useState (race conditions, no caching)
const [devices, setDevices] = useState([]);
useEffect(() => {
fetch('/api/devices').then(res => setDevices(res));
}, []);// DeviceCard.module.css
.card { background: white; border-radius: 8px; }
.card:hover { box-shadow: 0 4px 8px rgba(0,0,0,0.1); }
// DeviceCard.tsx
import styles from './DeviceCard.module.css';
<div className={styles.card}>...</div>Vite Config:
// vite.config.ts
export default defineConfig({
plugins: [react()],
server: {
port: 5173,
proxy: {
'/api': 'http://localhost:7777', // Proxy API to backend
},
},
build: {
outDir: 'dist',
sourcemap: true, // Debug production builds
},
test: {
globals: true,
environment: 'jsdom', // Simulate browser
setupFiles: './tests/setup.ts',
},
});Reason for rejection: Team has more React experience, React ecosystem is larger.
Reason for rejection: Smaller ecosystem, less TypeScript support, unfamiliarity.
Reason for rejection: Too much boilerplate for complex state management, routing, forms.
Reason for rejection: Slow builds (Webpack), deprecated by React team. Vite is 10-100x faster.
Reason for rejection: Overkill for local dashboard. SSR not needed (no SEO required).
If we outgrow this stack:
- Switch to Next.js: If we add user auth, multi-page app.
- Add Redux: If client-side state becomes too complex (unlikely).
- Migrate to Remix: If we need advanced routing/data loading.
| Metric | Target | Actual (v0.2.0) |
|---|---|---|
| Bundle Size | <500KB gzipped | 380KB ✅ |
| First Contentful Paint | <1.5s | 1.2s ✅ |
| Time to Interactive | <3s | 2.1s ✅ |
| HMR Speed | <100ms | 50ms ✅ |
| Test Execution | <5s | 3.2s ✅ |
- See ADR-001 for backend Clean Architecture
- See ADR-005 for API design
- See ADR-006 for testing strategy
🇩🇪 Benutzerhandbuch
🇬🇧 User Guide
Development
API & Architecture
- REST API
- ADR 001 Clean Architecture
- ADR 002 FastAPI App State
- ADR 003 SSDP Discovery
- ADR 004 React/TS/Vite
Technical Reference
Legal