Reactive Dashboard Performance
Expert in building production-grade reactive dashboards that load in <100ms and have comprehensive test coverage.
Core Expertise
Performance Patterns (Linear, Vercel, Notion-grade)
- Skeleton-First Loading
- Render skeleton immediately (0ms perceived load) - Stream in data progressively - Never show spinners for <200ms loads
- Aggressive Caching
- React Query with staleTime: 5min, cacheTime: 30min - Optimistic updates for mutations - Prefetch on hover/mount
- Code Splitting
- Route-based splitting (Next.js automatic) - Component-level lazy() for heavy widgets - Preload critical paths
- Memoization Strategy
- useMemo for expensive computations - React.memo for pure components - useCallback for stable references
Testing Reactive Dashboards
- Mock Strategy
- Mock at service boundary (React Query, analytics) - Never mock UI components (test real DOM) - Use MSW for API mocking when possible
- Async Handling
// WRONG - races with React render(<Dashboard />); const element = screen.getByText('Welcome'); // RIGHT - waits for async resolution render(<Dashboard />); const element = await screen.findByText('Welcome'); - Timeout Debugging
- Timeouts mean: missing mock, wrong query, or component not rendering - Use screen.debug() to see actual DOM - Check console for unmocked errors
- Test Wrapper Pattern
const TestProviders = ({children}) => (<QueryClientProvider client={testQueryClient}> <AuthProvider> {children} </AuthProvider> </QueryClientProvider>);
Real-World Examples
- Linear Dashboard: Skeleton → Stale data → Fresh data (perceived <50ms)
- Vercel Dashboard: Prefetch on nav hover, optimistic deploys
- Notion Pages: Infinite cache, local-first, sync in background
Diagnostic Protocol
Integration Test Timeouts
- Check what's actually rendering
render(<Component />); screen.debug(); // See actual DOM - Find unmocked dependencies
- Check console for "not a function" errors - Look for network requests in test output - Verify all contexts are provided
- Fix async queries
- Use findBy* instead of getBy* - Increase timeout if needed: waitFor(() => {...}, {timeout: 3000}) - Mock React Query properly
- Simplify component tree
- Test widgets individually first - Add full integration tests last - Use data-testid for complex queries
Performance Optimization
Dashboard Load Budget
| Phase | Target |
|---|---|
| Skeleton render | 0-16ms (1 frame) |
| First data paint | <100ms |
| Full interactive | <200ms |
| Lazy widgets | <500ms |
React Query Config
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5min
cacheTime: 30 * 60 * 1000, // 30min
refetchOnWindowFocus: false,
refetchOnMount: false,
retry: 1,
},
},
});Skeleton Pattern
function Dashboard() {
const { data, isLoading } = useQuery('dashboard', fetchDashboard);
// Show skeleton immediately, no loading check
return (
<div>
{data ? <RealWidget data={data} /> : <SkeletonWidget />}
</div>
);
}Common Pitfalls
- Spinners for fast loads - Use skeletons instead
- Unmemoized expensive computations - Wrap in useMemo
- Testing implementation details - Test user behavior
- Mocking too much - Mock at boundaries only
- Synchronous test expectations - Everything is async
When debugging test timeouts, ALWAYS start with screen.debug() to see what actually rendered.