F1 Visor is a single-page application (SPA) built with React that visualizes Formula 1 race data from the OpenF1 API. The application follows a modular architecture with clear separation of concerns.
┌─────────────────────────────────────────────────────────┐
│ Browser (Client) │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ React Application │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ Components │ │ Stores │ │ │
│ │ │ (View) │◄─┤ (Zustand) │ │ │
│ │ └──────┬───────┘ └──────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ Custom Hooks │◄─┤ React Query │ │ │
│ │ └──────┬───────┘ └──────┬───────┘ │ │
│ │ │ │ │ │
│ │ ▼ ▼ │ │
│ │ ┌─────────────────────────────────┐ │ │
│ │ │ API Services (Axios) │ │ │
│ │ └─────────────┬───────────────────┘ │ │
│ └────────────────┼──────────────────────────────┘ │
│ │ │
└───────────────────┼────────────────────────────────────┘
│
│ HTTPS
▼
┌──────────────────────┐
│ OpenF1 API │
│ api.openf1.org/v1 │
└──────────────────────┘
Location: src/components/
Responsible for rendering UI and handling user interactions.
-
Circuit Components (
circuit/)CircuitMap.tsx- Canvas-based 2D circuit rendererCarMarker.tsx- Individual car position markerCircuitControls.tsx- Zoom/pan controls
-
Telemetry Components (
telemetry/)TelemetryPanel.tsx- Main telemetry containerSpeedGauge.tsx- Speed indicatorRPMGauge.tsx- RPM indicatorGearDisplay.tsx- Current gear displaySpeedChart.tsx- Speed over time graph
-
Position Components (
positions/)PositionsTable.tsx- Driver standings tableDriverRow.tsx- Individual driver rowIntervalDisplay.tsx- Time gap display
-
Session Components (
session/)SessionSelector.tsx- Session pickerSeasonFilter.tsx- Year selectorCircuitFilter.tsx- Circuit selector
-
UI Components (
ui/)Button.tsx- Reusable buttonCard.tsx- Container cardLoading.tsx- Loading spinnerErrorMessage.tsx- Error display
Location: src/stores/
Uses Zustand for global state management.
// raceStore.ts
interface RaceStore {
selectedDriver: number | null
playbackSpeed: number
currentTime: Date | null
isPlaying: boolean
selectedSession: number | null
}
// uiStore.ts
interface UIStore {
sidebarOpen: boolean
selectedTab: 'telemetry' | 'positions' | 'strategy'
mapZoom: number
}Location: src/hooks/ and src/services/
useSessionData()- Fetch session metadatauseLocationData()- Fetch GPS coordinatesusePositionData()- Fetch race positionsuseLapData()- Fetch lap timesuseTelemetryData()- Fetch car telemetryusePitData()- Fetch pit stops
// services/openf1/client.ts
export const apiClient = axios.create({
baseURL: 'https://api.openf1.org/v1',
timeout: 10000,
})
// services/openf1/sessions.ts
export const getSessions = (filters: SessionFilters) => {...}
export const getSession = (sessionKey: number) => {...}
// services/openf1/location.ts
export const getLocationData = (sessionKey: number, filters: LocationFilters) => {...}
// ... other service modulesLocation: src/utils/
Pure functions for data transformation and calculations.
-
coordinates.ts - GPS to canvas conversion
export const gpsToCanvas = (lat: number, lng: number, bounds: Bounds): Point export const calculateBounds = (locations: Location[]): Bounds
-
time.ts - Time manipulation
export const parseRaceTime = (timeString: string): Date export const formatLapTime = (milliseconds: number): string export const interpolateTime = (start: Date, end: Date, progress: number): Date
-
colors.ts - Team color mapping
export const getTeamColor = (teamName: string): string
Location: src/types/
TypeScript interfaces and types for the entire application.
// types/openf1.ts
export interface Session {
session_key: number
session_name: string
date_start: string
date_end: string
circuit_short_name: string
}
export interface LocationData {
date: string
driver_number: number
lat: number
lng: number
x: number
y: number
z: number
}
// ... other typesUser Opens App
↓
SessionSelector Component Mounts
↓
useSessionData Hook Fetches Sessions
↓
React Query Caches Results
↓
User Selects Session
↓
Store Updates (selectedSession)
User Clicks Play
↓
Store Updates (isPlaying: true)
↓
Playback Loop Starts (useEffect)
↓
currentTime Increments
↓
Components React to Time Change
↓
├─► CircuitMap: Fetches location data for currentTime
├─► TelemetryPanel: Fetches car_data for currentTime
├─► PositionsTable: Fetches position data for currentTime
└─► PitStopsPanel: Checks for pit stops at currentTime
↓
Canvas Re-renders with New Positions
React Query handles caching with the following strategy:
- Stale Time: 5 minutes (data doesn't change)
- Cache Time: 10 minutes
- Refetch on Window Focus: Disabled (historical data)
- Retry: 1 attempt
- Use
requestAnimationFramefor smooth animations - Implement dirty checking to avoid unnecessary redraws
- Use off-screen canvas for complex drawings
- Batch API requests when possible
- Use time-based filtering to reduce payload size
- Implement virtual scrolling for large lists
- Minimize re-renders with proper memoization
- Use
useMemofor expensive calculations - Use
useCallbackfor event handlers
- Lazy load heavy components (charts, maps)
- Split by route if multiple pages added
- Dynamic imports for large libraries
- No authentication required (public API)
- HTTPS only for API calls
- No sensitive data stored
- CORS handled by OpenF1 API
- Client-side only (no backend)
- Limited by browser memory for large datasets
- No real-time WebSocket support yet
- Add service worker for offline support
- Implement WebSocket for live data
- Add backend proxy for API rate limiting
- Database for user preferences
- Component-based architecture
- Large ecosystem
- Excellent TypeScript support
- Virtual DOM for performance
- Simpler API
- Less boilerplate
- Better TypeScript inference
- Smaller bundle size
- Built-in caching
- Automatic refetching
- Loading/error states
- Optimistic updates
- Better performance for many elements (20 cars)
- Easier animation with requestAnimationFrame
- Lower memory footprint
- Simpler coordinate transformations
-
Feature Development
- Create types in
src/types/ - Implement API service in
src/services/ - Create custom hook in
src/hooks/ - Build component in
src/components/ - Update store if needed
- Create types in
-
Testing Strategy
- Unit tests for utilities
- Integration tests for API services
- Component tests with React Testing Library
- E2E tests for critical flows (future)
-
Documentation
- Update AGENTS.md for new patterns
- Update README.md for new features
- Add JSDoc comments for complex functions
- Update this ARCHITECTURE.md for structural changes