Skip to Content
DocsDeveloper GuideDebugging & Performance

Debugging and Performance

Debugging your App

The primary tools for debugging your app are Chrome DevTools (for DOM/console/network) and command-line logging.

Using Chrome DevTools

For detailed information on connecting Chrome DevTools to your WebF app, see the Development Workflow section.

With Chrome DevTools connected, you can:

  • Console: View console.log(), console.error(), and other console messages
  • Elements: Inspect the DOM tree, view computed styles, and modify elements in real-time
  • Network: Monitor network requests, view request/response headers and payloads
  • Application: Inspect localStorage, sessionStorage, and other storage APIs

Debugging Tips

Use Descriptive Console Logs

// ❌ Not helpful console.log(data); // ✅ Better console.log('User data received:', data); // ✅ Even better - use console methods console.group('API Response'); console.log('Status:', response.status); console.log('Data:', response.data); console.groupEnd();

Use Debugger Statements (with caveats)

While breakpoints in Chrome DevTools are not yet supported, you can use strategic console.log statements to understand code flow:

function processData(data) { console.log('Processing data:', data); const result = transform(data); console.log('After transform:', result); return result; }

Inspect Component State

For React, you can log component state and props:

function MyComponent({ user }) { const [count, setCount] = useState(0); useEffect(() => { console.log('MyComponent state:', { count, user }); }, [count, user]); return <div>...</div>; }

Common Issues and Solutions

Elements Not Measuring Correctly

Problem: getBoundingClientRect() returns zeros or incorrect values.

Solution: Use the onscreen event or useFlutterAttached hook to ensure the element is rendered before measuring.

import { useFlutterAttached } from '@openwebf/react-core-ui'; function MyComponent() { const ref = useFlutterAttached(() => { // Safe to measure here const rect = ref.current.getBoundingClientRect(); console.log('Element size:', rect.width, rect.height); }); return <div ref={ref}>Content</div>; }

Network Requests Failing

Problem: Fetch requests fail or return CORS errors.

Solutions:

  • Check that your API server is running
  • Ensure CORS headers are properly configured on your backend
  • Verify the URL is correct
  • Check network logs in Chrome DevTools
// Add error handling fetch('https://api.example.com/data') .then(response => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }) .catch(error => { console.error('Fetch error:', error); });

Performance Profiling

Performance Monitoring in Code

You can also add custom performance markers in your code:

// Mark the start of an operation performance.mark('data-fetch-start'); await fetch('https://api.example.com/data') .then(r => r.json()) .then(data => { // Mark the end performance.mark('data-fetch-end'); // Measure the duration performance.measure( 'data-fetch', 'data-fetch-start', 'data-fetch-end' ); const measure = performance.getEntriesByName('data-fetch')[0]; console.log(`Data fetch took ${measure.duration}ms`); });

Best Practices

1. Leverage Async Rendering

WebF’s async rendering model automatically batches DOM updates for better performance. DOM mutations in WebF are 20x cheaper than in browsers because updates are batched and processed in the next frame.

// ✅ Multiple DOM updates are automatically batched function updateUI(items) { items.forEach(item => { const element = document.createElement('div'); element.textContent = item.name; container.appendChild(element); // All appends batched automatically }); }

Note: Unlike browsers, you don’t need DocumentFragment optimization in WebF - the async rendering model handles batching for you.

For very long lists (hundreds or thousands of items), use WebFListView for virtualization and optimal rendering performance.

2. Use the onscreen Event

Defer expensive operations until elements are actually rendered:

import { useFlutterAttached } from '@openwebf/react-core-ui'; function LazyImage({ src }) { const ref = useFlutterAttached(() => { // Load image only when element is rendered ref.current.src = src; }); return <img ref={ref} alt="" />; }

3. Use Native Components for Performance-Critical UI

For complex animations, large lists, or performance-sensitive UI, prefer Flutter widgets over pure web implementations:

import { FlutterCupertinoButton } from '@openwebf/react-cupertino-ui'; // ✅ High-performance native button with perfect animations function MyButton() { return <FlutterCupertinoButton onClick={handleClick}>Click</FlutterCupertinoButton>; }

4. Optimize Images and Assets

  • Use appropriate image formats (WebP for photos, SVG for icons)
  • Compress images before including them
  • Use lazy loading for images below the fold
  • Consider using native image components for better performance
// ✅ Simple lazy loading with native loading attribute function ProductImage({ src, alt }) { return <img src={src} alt={alt} loading="lazy" />; } // ✅ Lazy loading in HTML <img src="/path/to/image.jpg" alt="Product" loading="lazy" />

The loading="lazy" attribute is the recommended approach for lazy loading images in WebF.

5. Minimize Native Binding Calls

While the Native Binding System is optimized, be mindful of:

// ❌ Bad - frequent small calls for (let i = 0; i < 1000; i++) { nativePlugin.saveItem(items[i]); } // ✅ Good - batch operations nativePlugin.saveItems(items);

6. Use CSS Transforms for Animations

CSS transforms are highly optimized and run on the compositor thread:

/* ✅ Good - hardware accelerated */ .animated { transition: transform 0.3s; } .animated:hover { transform: scale(1.1); } /* ⚠️ Less efficient - causes layout recalculation */ .animated { transition: width 0.3s; }

7. Debounce Expensive Operations

For operations triggered by user input, use debouncing:

import { useState, useEffect } from 'react'; function SearchComponent() { const [query, setQuery] = useState(''); const [results, setResults] = useState([]); useEffect(() => { // Debounce search const timer = setTimeout(() => { if (query) { performSearch(query).then(setResults); } }, 300); return () => clearTimeout(timer); }, [query]); return ( <div> <input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Search..." /> <ResultsList results={results} /> </div> ); }

8. Monitor Bundle Size

Keep your JavaScript bundle size small:

# Analyze bundle size with Vite npm run build -- --mode analyze # Check what's in your bundle npx vite-bundle-visualizer

9. Code Splitting

Split your code into smaller chunks that load on demand:

import { lazy, Suspense } from 'react'; // Lazy load components const HeavyComponent = lazy(() => import('./HeavyComponent')); function App() { return ( <Suspense fallback={<div>Loading...</div>}> <HeavyComponent /> </Suspense> ); }

10. Use Production Builds for Testing

Always test performance with production builds:

npm run build npm run preview

Development builds include extra code for debugging and hot reloading that significantly impacts performance.

Performance Checklist

  • App maintains 60 FPS during normal usage
  • No layout thrashing (rapid read/write of DOM properties)
  • Images are optimized and lazy-loaded
  • Bundle size is reasonable (< 500KB for initial load)
  • Code splitting is implemented for large apps
  • Expensive operations are debounced or throttled
  • Native components are used for performance-critical UI
  • CSS transforms are used for animations
  • Production build is tested before release
  • Network requests are cached when appropriate