21 KiB
Domain Pitfalls: RTU Web Interface on Raspberry Pi
Domain: IoT/RTU Monitoring Dashboard (Raspberry Pi Zero 2 W/3B, Chromium Kiosk) Researched: 2026-03-13 Confidence: HIGH
Critical Pitfalls
Pitfall 1: Ignoring Hardware Performance Constraints
What goes wrong: Developers build web interfaces targeting powerful development machines, then deploy to Pi Zero 2 W (1GHz quad-core, 512MB RAM) where the same code runs at 2-5 FPS with 10+ second load times. Dashboard becomes unresponsive, browser crashes due to memory pressure, or system OOM killer terminates Chromium.
Why it happens:
- Development on modern laptops/desktops masks performance issues
- React dev mode is significantly slower than production builds
- No testing on actual target hardware until late in project
- Assuming "lightweight" libraries are automatically Pi-friendly
- Not accounting for Chromium's baseline overhead (~150-200MB RAM)
How to avoid:
- Set hard performance budgets: <170KB initial JS, <2s LCP, <100ms INP on Pi Zero 2 W
- Test on actual hardware weekly, not just at end
- Use
npm run buildand serve production builds for all testing - Enable Chrome DevTools Performance throttling (6x CPU slowdown approximates Pi Zero)
- Monitor memory:
htopshould show Chromium <300MB RSS on dashboard view - Avoid heavy charting libraries (Chart.js, D3) - use lightweight alternatives (uPlot, custom SVG)
Warning signs:
- Load times >5 seconds on development machine = will be unusable on Pi
- Lighthouse performance score <90 on dev machine = critical issues on Pi
- Chromium tab showing "Page Unresponsive" or "Kill" dialog
- System using swap (check
free -h)
Phase to address: Phase 1 (MVP Dashboard) - Set performance budgets before writing any UI code. Profile first render of dashboard on Pi hardware before adding any features.
Pitfall 2: Unbounded Real-Time Data Accumulation
What goes wrong: Dashboard polls for sensor data every 1-5 seconds, stores all historical data in React state/memory for "trending". After 24-48 hours of operation, browser memory usage grows continuously until crash or severe slowdown. Page refreshes become slower over time.
Why it happens:
- Not implementing data retention/cleanup strategies
- Assuming "it's just numbers, can't be that big"
- Storing raw data in component state instead of persistent storage
- Not accounting for continuous 24/7 operation requirement
- Missing memory leak cleanup in useEffect hooks
How to avoid:
- Implement circular buffer pattern: keep only last N data points (e.g., 1000 points = ~20 minutes at 1 sample/sec)
- Store historical data in SQLite/file system, not browser memory
- Use Web Workers for data processing to avoid blocking UI thread
- Implement explicit cleanup in useEffect return functions
- Add
document.visibilitycheck - pause polling when tab hidden - Set maximum array sizes and implement FIFO eviction
Warning signs:
- Chrome DevTools Memory tab shows JS heap growing over time
- Dashboard gets slower the longer it runs
window.performance.memory.usedJSHeapSizeincreasing steadily- Frame drops correlated with data array length
Phase to address: Phase 2 (Data Persistence) - Design data architecture with bounded memory from day one. Implement circular buffers before adding real-time charts.
Pitfall 3: Layout Thrashing from Unoptimized Re-renders
What goes wrong: Real-time sensor updates trigger React re-renders of entire component tree. Each render causes browser layout recalculation (forced reflow). On Pi Zero with limited CPU, this creates jank/jerky animations, missed frames, and unresponsive UI during data updates.
Why it happens:
- State updates in parent component cause cascading re-renders
- Not using React.memo on child components that receive unchanged props
- Mixing data updates with animation frame updates
- Reading layout properties (offsetHeight, getBoundingClientRect) during render
- Animating layout properties (width, height, top, left) instead of transform/opacity
How to avoid:
- Memoize components:
const SensorCard = React.memo(...)for dashboard widgets - Use
useMemofor expensive calculations (statistics, derived values) - Colocate state - keep sensor state in individual card components, not global
- Use CSS transforms instead of layout properties for animations
- Virtualize long lists (settings menus) using fixed-height items
- Separate display data from update logic - debounce visual updates to 30fps max
- Use
requestAnimationFramefor smooth animations, not setInterval
Warning signs:
- Chrome DevTools Performance shows long "Recalculate Style" or "Layout" blocks
- Purple bars in performance timeline indicating style/layout work
- Jank during sensor updates (visible stuttering)
- React DevTools Profiler shows unexpected re-renders
Phase to address: Phase 1 (MVP Dashboard) - Profile render performance early. Apply React.memo to dashboard cards before adding more than 3-4 widgets.
Pitfall 4: Blocking Main Thread with Synchronous Operations
What goes wrong: CSV file operations, data serialization, or sensor data processing runs on main thread. During these operations (which can take 100-500ms on Pi Zero), UI becomes completely unresponsive - touch inputs are queued but not processed until operation completes.
Why it happens:
- JavaScript is single-threaded by default
- File operations via FileReader or synchronous XHR block
- Large array operations (sort, filter, map) on sensor data
- CSV parsing/stringification without chunking
- Not using Web Workers for CPU-intensive tasks
How to avoid:
- Use Web Workers for all CSV processing, data serialization
- Yield to main thread: break large operations into chunks with
setTimeout(..., 0) - Use async/await with requestIdleCallback for non-critical processing
- Implement progressive loading for file manager view
- Stream CSV data instead of loading entire file into memory
- Use
performance.mark()to identify long tasks (>50ms)
Warning signs:
- INP (Interaction to Next Paint) >500ms
- Touch inputs have noticeable delay
- "Long Task" warnings in Chrome DevTools
- Frame drops coincide with data processing
Phase to address: Phase 3 (CSV Workflow) - All file operations must be async with progress indicators. Move CSV processing to Web Worker before implementing full workflow.
Pitfall 5: Kiosk Mode UI Anti-Patterns
What goes wrong: Dashboard designed for mouse/keyboard interaction is deployed to 7" touchscreen. Buttons too small for finger targeting (44px minimum missed), hover states don't work, scrolling is jerky, no touch feedback, back button missing, users get "trapped" in deep settings menus with no exit.
Why it happens:
- Designing on desktop with mouse, not testing touch interaction
- Using hover-only interactions (dropdown menus, tooltips)
- Not accounting for finger occlusion (finger covers target when tapping)
- Missing touch-specific affordances (active states, haptic feedback)
- No "emergency exit" from deep navigation
How to avoid:
- Minimum touch target: 48x48px (Apple HIG) or 44x44px (Material Design)
- Add 8px spacing between interactive elements
- Use
:activepseudo-class, not just:hover - Implement swipe gestures for back navigation
- Add visible touch feedback (scale down briefly on press)
- Always show back button in settings sub-pages
- Test with actual fingers, not mouse, on target display size
- Disable zoom/pinch in kiosk mode (
user-scalable=no)
Warning signs:
- Users mis-tap frequently in testing
- Elements feel "cramped" on 7" display
- Navigation requires precise aiming
- Users don't know elements are interactive
Phase to address: Phase 1 (MVP Dashboard) - Design for touch from day one. Test all interactions on actual touchscreen before finalizing component library.
Pitfall 6: Not Handling Browser Resource Throttling
What goes wrong: Dashboard works perfectly during active use, but when left idle for hours, Chromium throttles timers (setInterval/setTimeout), pauses animations, and suspends background processing. Real-time sensor data stops updating, charts freeze, or periodic data transmission fails silently.
Why it happens:
- Chromium aggressively throttles background tabs to save battery/CPU
- Page Visibility API not implemented
- Assuming
setIntervalwill fire reliably every N seconds - Not using WebSockets or persistent connections
- Background timers throttled to 1% of normal rate after 5+ minutes hidden
How to avoid:
- Implement Page Visibility API - adjust behavior when
document.hidden - Use
navigator.sendBeaconfor critical data transmission - For continuous monitoring: use Web Workers (not throttled like main thread)
- Consider Page Lifecycle API for freeze/resume handling
- Don't rely on
setIntervalfor critical timing - use system time diff - Add visual indicator showing "live" vs "stale" data
Warning signs:
- Data timestamps show gaps when tab was backgrounded
- Charts show flat lines during periods of inactivity
setIntervalcallbacks firing much less frequently than specified- Data transmission failures accumulate over time
Phase to address: Phase 4 (Network Stack) - Implement visibility-aware polling before adding network features. Test long-running behavior with tab backgrounding.
Pitfall 7: Memory Leaks from Event Listeners and Subscriptions
What goes wrong: Component mounts add event listeners, WebSocket connections, or setInterval timers. When component unmounts (navigating between settings pages), these are not cleaned up. Memory usage grows with each navigation cycle. After days of use, browser becomes sluggish or crashes.
Why it happens:
- Forgetting cleanup in useEffect return function
- Not closing WebSocket connections on unmount
- Global event listeners added but never removed
- Subscriptions to external data sources not cancelled
- Closures capturing large objects preventing GC
How to avoid:
- Always return cleanup function from useEffect:
useEffect(() => { const ws = new WebSocket(url); return () => ws.close(); // Cleanup! }, []); - Use
AbortControllerfor fetch cancellation - Remove all addEventListener with matching removeEventListener
- Clear all intervals/timeouts in cleanup
- Use React StrictMode during development (double-mounts expose leaks)
- Test by navigating between all routes 50+ times, check memory in DevTools
Warning signs:
- Memory usage increases with each route change
- Multiple WebSocket connections shown in DevTools Network tab
- Event listeners count increasing in DevTools Performance
- Component unmounts but callbacks still execute
Phase to address: Phase 1 (MVP Dashboard) - Audit all useEffect hooks for cleanup before adding multiple settings views. Use StrictMode from project start.
Pitfall 8: Choking the Pixel Pipeline with Expensive CSS
What goes wrong: Dashboard uses heavy CSS effects (box-shadow, backdrop-filter, complex gradients) that trigger expensive paint/composite operations. On Pi Zero's GPU, this results in dropped frames during scrolling, slow animations, and battery drain. Visual effects designed for desktop GPUs cripple embedded GPU.
Why it happens:
- Modern design systems (shadcn/ui, Tailwind) include heavy effects by default
- Not understanding browser pixel pipeline (JS → Style → Layout → Paint → Composite)
- Animating expensive properties (width, height, top, left) triggers layout
- Overuse of
backdrop-filter: blur()- very expensive on Mali-400 GPU - Layer explosion from
will-changemisuse
How to avoid:
- Prefer
transformandopacityfor animations (composite-only) - Avoid
backdrop-filterentirely on Pi Zero (software fallback) - Minimize box-shadow complexity (smaller blur radius, solid colors)
- Use Chrome DevTools Layers panel to diagnose layer count
- Test with "Rendering" > "Paint flashing" enabled - minimize green flashes
- Limit simultaneous animations to 2-3 elements max
- Use
contain: layout paintto isolate expensive subtrees
Warning signs:
- Scrolling is jerky/stuttering
- Animations dropping frames (visible "steps" instead of smooth)
- High "Paint" or "Composite" times in DevTools Performance
- GPU process using excessive CPU (software fallback)
Phase to address: Phase 1 (MVP Dashboard) - Profile CSS performance on Pi hardware. Strip heavy effects before adding visual polish.
Technical Debt Patterns
| Shortcut | Immediate Benefit | Long-term Cost | When Acceptable |
|---|---|---|---|
| Store all sensor data in React state | Fastest to implement | Memory grows unbounded, crashes after days | Never for 24/7 operation |
| Use Chart.js for real-time charts | Rich features out of box | 200KB+ bundle, heavy CPU on animation | Only if data rate <1 sample/minute |
| Synchronous file operations | Simpler code structure | UI freezes during CSV export | Never - use Web Workers |
| Disable React StrictMode | Fewer console warnings, faster dev | Missed memory leaks, unsafe lifecycle issues | Never |
| Inline all SVG icons | No build complexity | Larger bundle, can't cache | For <20 icons only |
| Skip touch testing on desktop | Faster iteration | Complete rework for kiosk deployment | Only paper prototypes |
| Use CSS backdrop-filter | Modern glass-morphism look | 10-50x slower on Pi Zero GPU | Never on constrained hardware |
Integration Gotchas
| Integration | Common Mistake | Correct Approach |
|---|---|---|
| WebSocket Sensor Data | Opening new connection on every component mount, no reconnection logic | Single shared connection, exponential backoff reconnection, cleanup on unmount |
| Local Storage Settings | Storing large objects (>5MB), no versioning/migration | JSON with schema version, compression for large configs, validation on load |
| CSV File Upload | Loading entire file into memory, synchronous parsing | Stream processing with FileReader chunks, progress indicators, validation |
| Network Status Detection | Polling server continuously for "online" status | Listen to navigator.onLine events, with fallback heartbeat every 30s max |
| Chromium Kiosk Mode | Assuming window focus = page visible | Use Page Visibility API, handle background throttling explicitly |
| Touch Events | Using only mouse events (click, mousedown) | Add touchstart/touchend handlers, 300ms click delay on mobile, use pointer events |
Performance Traps
| Trap | Symptoms | Prevention | When It Breaks |
|---|---|---|---|
| Unthrottled Data Updates | UI freezes when new sensor data arrives | Debounce display updates to 30fps max, batch state updates | >10 data points/sec |
| Unvirtualized Long Lists | Settings menu scrolls at 5 FPS, memory high | Use react-window or fixed-height virtualization | >50 list items |
| Re-rendering Entire Tree | Jank when any sensor updates | Colocate state, use React.memo, split components | >5 dashboard widgets |
| Main Thread File Operations | 2-5 second UI freeze on CSV export | Move to Web Worker, show progress bar | Files >100KB |
| Synchronous LocalStorage | 100-500ms pauses during settings save | Use async storage API, optimistic UI updates | Settings accessed frequently |
| Unbounded setInterval | Memory leaks, orphaned timers | Always clearInterval in cleanup, use requestAnimationFrame for visual updates | Component unmount/remount cycles |
Security Mistakes
| Mistake | Risk | Prevention |
|---|---|---|
| No input validation on CSV import | Malformed CSV crashes parser, potential code execution | Schema validation, sanitize all inputs, handle errors gracefully |
| Exposing WebSocket without auth | Unauthorized clients can connect, inject data | Implement connection validation, rate limiting, origin checking |
| Storing credentials in localStorage | XSS attack can steal credentials | Use HttpOnly cookies, implement proper session management |
| No CSP headers in kiosk mode | XSS via injected scripts | Set Content-Security-Policy, disable inline scripts |
| File Manager without path validation | Directory traversal attacks | Whitelist allowed directories, sanitize paths, chroot if possible |
| Logging sensitive data | Credentials in logs, information disclosure | Sanitize logs, never log passwords or keys |
UX Pitfalls
| Pitfall | User Impact | Better Approach |
|---|---|---|
| No loading states | User thinks interface is broken during data load | Skeleton screens, progress indicators, spinner on all async operations |
| Missing touch feedback | User unsure if tap registered | Scale down briefly on touch, ripple effect, haptic feedback if available |
| Tiny touch targets | Mis-taps, frustration | Minimum 48x48px targets, 8px spacing |
| Hidden navigation | Users get trapped in settings, can't go back | Always-visible back button, breadcrumb trail on deep pages |
| No offline indication | User unaware data not syncing | Banner indicator showing connection status, timestamp of last successful sync |
| Auto-refresh without warning | Lost form input, confusion | Warn before refresh, save draft state, preserve form data |
| Too many decimal places | Cluttered display, hard to read | Format numbers appropriately (2 decimals for voltage, 1 for rainfall) |
"Looks Done But Isn't" Checklist
- Dashboard Loads: Tested on actual Pi Zero 2 W hardware, not just dev machine
- Memory Bounded: Running for 24+ hours without memory growth (check
free -h) - Touch Optimized: All interactions tested with finger on 7" touchscreen
- Kiosk Configured: Chromium flags set for kiosk mode, no browser chrome visible
- Offline Resilient: Network failures handled gracefully, data queued for retry
- Cleanup Implemented: All useEffect hooks have proper cleanup functions
- Performance Budget Met: <170KB initial JS, <2s LCP on target hardware
- No Layout Thrashing: Chrome DevTools Performance shows minimal purple (layout) bars
- Visibility Aware: Page handles backgrounding/throttling correctly (sensors don't stop)
- Error Boundaries: React error boundaries prevent full white-screen crashes
- Input Validation: All external inputs (CSV, network, settings) validated before use
- Accessibility: Sufficient color contrast for outdoor/industrial viewing conditions
Recovery Strategies
| Pitfall | Recovery Cost | Recovery Steps |
|---|---|---|
| Memory leak discovered late | HIGH (weeks) | Profile with React DevTools Profiler + Chrome Memory tab, bisect component tree, add StrictMode retroactively |
| Unbounded data growth | MEDIUM (days) | Implement circular buffers, move historical data to SQLite, add data retention policy |
| Layout thrashing on Pi | MEDIUM (days) | Audit with Chrome DevTools Performance, apply React.memo strategically, colocate state, CSS transform optimization |
| Touch targets too small | LOW (hours) | Increase padding/margins, adjust CSS, test on actual hardware |
| Kiosk throttling data | LOW (hours) | Implement Page Visibility API, switch to Web Workers for background processing |
| Blocking main thread | MEDIUM (days) | Identify long tasks in DevTools, move processing to Web Workers, add yielding points |
| Heavy CSS effects | LOW (hours) | Remove backdrop-filter, reduce shadows, test with Paint flashing enabled |
Pitfall-to-Phase Mapping
| Pitfall | Prevention Phase | Verification |
|---|---|---|
| Hardware Performance Constraints | Phase 1 (MVP Dashboard) | Lighthouse score >90 on dev, <2s load on Pi Zero |
| Unbounded Data Accumulation | Phase 2 (Data Persistence) | Memory usage flat over 24h test, circular buffer implemented |
| Layout Thrashing | Phase 1 (MVP Dashboard) | React DevTools Profiler shows no unexpected re-renders, 60fps on Pi |
| Blocking Main Thread | Phase 3 (CSV Workflow) | All file ops async, Web Workers for processing, INP <200ms |
| Kiosk Mode UI Anti-Patterns | Phase 1 (MVP Dashboard) | All interactions pass touch testing on 7" display |
| Browser Resource Throttling | Phase 4 (Network Stack) | Data updates continue when tab backgrounded for 1+ hours |
| Memory Leaks | Phase 1 (MVP Dashboard) | StrictMode enabled, 50x route navigation test shows stable memory |
| Expensive CSS | Phase 1 (MVP Dashboard) | Chrome Paint flashing shows minimal repaints, 60fps scroll |
Sources
- MDN Web Performance - HIGH confidence
- web.dev Performance Budgets 101 - HIGH confidence
- web.dev Optimize LCP - HIGH confidence
- web.dev Optimize INP - HIGH confidence
- web.dev Rendering Performance - HIGH confidence
- MDN Page Visibility API - HIGH confidence
- React Documentation - Render and Commit - HIGH confidence
- Raspberry Pi Zero 2 W Hardware Specs (1GHz quad-core ARM Cortex-A53, 512MB RAM) - HIGH confidence
- Chromium Kiosk Mode documentation and resource throttling behavior - MEDIUM confidence
- Industrial touchscreen UX guidelines (44-48px touch targets) - MEDIUM confidence
Pitfalls research for: RTU Web Interface (TCKRTUIYO) Researched: 2026-03-13