Grid Performance Optimization Guide
This guide covers best practices, optimization techniques, and performance monitoring for TachUI's Grid components to ensure optimal performance across all use cases.
Performance Architecture Overview
TachUI's Grid system is built for performance with several key optimizations:
- Native CSS Grid: Leverages browser's optimized layout engine
- CSS Caching: LRU cache with 95%+ hit rates for repeated configurations
- Efficient Rendering: Minimal JavaScript overhead with DOM-based updates
- Smart Defaults: Performance-optimized default configurations
- Memory Management: Automatic cleanup and garbage collection
Core Optimization Principles
1. Choose the Right Grid Component
Each Grid component is optimized for specific use cases:
// ✅ Use Grid for simple, predictable layouts
const SimpleLayout = Grid({
children: [
GridRow([Header(), Navigation()]),
GridRow([Sidebar(), MainContent()])
]
})
// ✅ Use LazyVGrid for vertical scrolling with many items
const ProductCatalog = LazyVGrid({
columns: [GridItem.adaptive(250, 350)],
children: products.map(p => ProductCard(p))
})
// ✅ Use LazyHGrid for horizontal scrolling carousels
const ImageCarousel = LazyHGrid({
rows: [GridItem.fixed(200)],
children: images.map(img => ImageCard(img))
})2. Optimize GridItem Configurations
GridItem configuration directly impacts performance:
// ✅ Good: Constrained flexible columns
const OptimizedGrid = LazyVGrid({
columns: [
GridItem.flexible(200, 400), // Clear min/max bounds
GridItem.adaptive(250) // Reasonable minimum
],
children: items
})
// ❌ Avoid: Unconstrained flexible columns on large datasets
const UnoptimizedGrid = LazyVGrid({
columns: [
GridItem.flexible(), // Can cause layout thrashing
GridItem.flexible()
],
children: thousandsOfItems // Performance issue with large datasets
})3. Leverage CSS Caching
The Grid system automatically caches CSS for repeated configurations:
// ✅ Reuse common configurations for optimal caching
const commonColumns = [
GridItem.flexible(200, 300),
GridItem.flexible(200, 300),
GridItem.flexible(200, 300)
]
// These grids will reuse cached CSS
const Grid1 = LazyVGrid({ columns: commonColumns, children: items1 })
const Grid2 = LazyVGrid({ columns: commonColumns, children: items2 })
const Grid3 = LazyVGrid({ columns: commonColumns, children: items3 })
// Cache hit rate: ~95% for repeated configurationsPerformance Best Practices
1. Efficient Item Rendering
Optimize individual grid items for better overall performance:
// ✅ Memoized item components
const MemoizedProductCard = memo(({ product }: { product: Product }) =>
VStack([
Image({ src: product.thumbnail, alt: product.name })
.modifier
.aspectRatio(1, 'fill')
.cornerRadius(8)
.build(),
Text(product.name)
.modifier
.fontWeight('bold')
.lineClamp(2)
.build()
])
.modifier
.padding(12)
.backgroundColor('#ffffff')
.cornerRadius(8)
.build()
)
// ✅ Use memoized components in grids
const ProductGrid = LazyVGrid({
columns: [GridItem.adaptive(250, 350)],
children: products.map(product =>
MemoizedProductCard({ product, key: product.id })
)
})2. Virtual Scrolling for Large Datasets
Implement virtual scrolling for grids with thousands of items:
// ✅ Virtual scrolling implementation
const VirtualGrid = ({ items }: { items: Item[] }) => {
const [visibleRange, setVisibleRange] = useState({ start: 0, end: 50 })
const itemHeight = 300 // Approximate item height
const containerRef = useRef<HTMLElement>()
const handleScroll = useCallback((event: Event) => {
const scrollTop = (event.target as HTMLElement).scrollTop
const viewportHeight = (event.target as HTMLElement).clientHeight
const start = Math.floor(scrollTop / itemHeight) - 5 // Buffer
const end = Math.ceil((scrollTop + viewportHeight) / itemHeight) + 5
setVisibleRange({ start: Math.max(0, start), end: Math.min(items.length, end) })
}, [items.length])
const visibleItems = items.slice(visibleRange.start, visibleRange.end)
return LazyVGrid({
columns: [GridItem.adaptive(250, 350)],
spacing: 16,
children: [
// Spacer for items before visible range
visibleRange.start > 0 &&
Spacer().modifier.height(visibleRange.start * itemHeight).build(),
// Visible items
...visibleItems.map(item => ItemComponent(item)),
// Spacer for items after visible range
visibleRange.end < items.length &&
Spacer().modifier.height((items.length - visibleRange.end) * itemHeight).build()
].filter(Boolean)
})
.modifier
.onScroll(handleScroll)
.build()
}3. Lazy Loading and Progressive Enhancement
Load content progressively to improve initial render performance:
// ✅ Progressive loading pattern
const ProgressiveGrid = ({ initialItems }: { initialItems: Item[] }) => {
const [items, setItems] = useState(initialItems)
const [isLoading, setIsLoading] = useState(false)
const [hasMore, setHasMore] = useState(true)
const loadMore = useCallback(async () => {
if (isLoading || !hasMore) return
setIsLoading(true)
try {
const newItems = await fetchMoreItems(items.length)
setItems(prev => [...prev, ...newItems])
setHasMore(newItems.length === BATCH_SIZE)
} finally {
setIsLoading(false)
}
}, [items.length, isLoading, hasMore])
// Intersection observer for infinite scroll
const loadMoreRef = useRef<HTMLElement>()
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => entry.isIntersecting && loadMore(),
{ threshold: 0.1 }
)
if (loadMoreRef.current) {
observer.observe(loadMoreRef.current)
}
return () => observer.disconnect()
}, [loadMore])
return LazyVGrid({
columns: [GridItem.adaptive(250, 350)],
spacing: 16,
children: [
...items.map(item => ItemComponent(item)),
// Loading indicator
hasMore && VStack([
isLoading ?
LoadingSpinner() :
Text('Load More').modifier.ref(loadMoreRef).build()
])
]
})
}4. Responsive Image Optimization
Optimize images within grid items:
// ✅ Responsive image loading
const OptimizedImageGrid = LazyVGrid({
columns: [GridItem.adaptive(200, 400)],
spacing: 12,
children: images.map(image =>
Image({
src: image.src,
// Responsive image sources
srcSet: `
${image.sizes.small} 200w,
${image.sizes.medium} 400w,
${image.sizes.large} 600w
`,
sizes: `
(max-width: 600px) 200px,
(max-width: 1200px) 400px,
600px
`,
alt: image.alt,
loading: 'lazy', // Native lazy loading
decoding: 'async' // Async image decoding
})
.modifier
.aspectRatio(image.aspectRatio || 1, 'fill')
.cornerRadius(8)
.build()
)
})Animation Performance
1. Efficient Animation Configuration
Choose appropriate animations for your use case:
// ✅ Lightweight animations for frequent updates
const FastGrid = LazyVGrid({
columns: [GridItem.adaptive(250)],
animations: GridAnimations.smoothLayout(200), // Fast layout changes only
children: items
})
// ⚠️ Use comprehensive animations sparingly
const HeavyGrid = LazyVGrid({
columns: [GridItem.adaptive(250)],
animations: GridAnimations.comprehensive(300, 400), // All animations enabled
children: items // Use only for small datasets or special cases
})2. GPU-Accelerated Animations
Ensure animations use hardware acceleration:
// ✅ GPU-accelerated transforms
const GPUOptimizedGrid = LazyVGrid({
columns: [GridItem.adaptive(250)],
animations: {
itemChanges: {
enter: {
duration: 250,
easing: 'ease-out',
from: 'scale' // Uses transform: scale() - GPU accelerated
}
},
layoutChanges: {
duration: 300,
easing: 'ease-out' // CSS Grid changes - browser optimized
}
},
children: items
})Memory Management
1. Automatic Cleanup
TachUI Grid components handle cleanup automatically:
// ✅ Automatic memory management
const GridComponent = ({ items }) => {
return LazyVGrid({
columns: [GridItem.adaptive(250)],
children: items.map(item => ItemComponent(item))
})
// Grid automatically cleans up:
// - Event listeners
// - CSS cache entries (LRU eviction)
// - DOM observers
// - Animation callbacks
}2. Memory Monitoring
Monitor memory usage in development:
// ✅ Development memory monitoring
const MonitoredGrid = LazyVGrid({
columns: [GridItem.adaptive(250)],
children: items,
// Enable performance monitoring in development
...(process.env.NODE_ENV === 'development' && {
debugLabel: 'ProductGrid',
performanceMonitoring: true
})
})
// Monitor performance metrics
if (process.env.NODE_ENV === 'development') {
// Grid performance metrics are logged to console
// - Render times
// - Memory usage
// - Cache hit rates
// - Layout calculations
}Performance Monitoring
1. Built-in Performance Tracking
TachUI Grid provides built-in performance monitoring:
import { GridPerformanceMonitor } from '@tachui/core'
// ✅ Enable performance tracking
const monitor = new GridPerformanceMonitor({
threshold: 16, // Alert if operations take >16ms (60fps target)
sampleRate: 0.1 // Sample 10% of operations
})
const PerformantGrid = LazyVGrid({
columns: [GridItem.adaptive(250)],
children: items,
performanceMonitor: monitor
})
// Performance data is automatically collected:
// - Layout calculation time
// - Rendering time
// - CSS cache performance
// - Memory usage patterns2. Performance Benchmarking
Compare different grid configurations:
// ✅ Benchmark different approaches
const benchmarkGridConfigurations = async () => {
const items = generateTestItems(1000)
// Configuration A: Fixed columns
const startA = performance.now()
const gridA = LazyVGrid({
columns: [
GridItem.fixed(250),
GridItem.fixed(250),
GridItem.fixed(250)
],
children: items
})
const endA = performance.now()
// Configuration B: Adaptive columns
const startB = performance.now()
const gridB = LazyVGrid({
columns: [GridItem.adaptive(250, 350)],
children: items
})
const endB = performance.now()
console.log(`Fixed columns: ${endA - startA}ms`)
console.log(`Adaptive columns: ${endB - startB}ms`)
}Real-World Optimization Examples
1. E-commerce Product Grid
Optimized for thousands of products:
const ProductGrid = ({ products }: { products: Product[] }) => {
// Memoized product card component
const ProductCard = memo(({ product }: { product: Product }) => (
VStack([
Image({
src: product.thumbnail,
srcSet: `${product.thumbnail} 1x, ${product.thumbnail2x} 2x`,
alt: product.name,
loading: 'lazy'
})
.modifier.aspectRatio(1, 'fill').cornerRadius(8).build(),
Text(product.name).modifier.fontWeight('bold').lineClamp(2).build(),
Text(`$${product.price}`).modifier.foregroundColor('#28a745').build()
])
.modifier.padding(12).backgroundColor('#ffffff').cornerRadius(8).build()
))
return LazyVGrid({
columns: [GridItem.adaptive(280, 350)], // Optimal for product cards
spacing: 16,
animations: GridAnimations.fadeIn(200), // Subtle entrance
accessibility: GridAccessibility.basic('Product Grid'),
children: products.map(product =>
ProductCard({ product, key: product.id })
)
})
.modifier
.padding(16)
.build()
}2. Social Media Feed
Optimized for infinite scroll and varying content heights:
const SocialFeed = ({ initialPosts }: { initialPosts: Post[] }) => {
const [posts, setPosts] = useState(initialPosts)
const [isLoading, setIsLoading] = useState(false)
// Memoized post component with size optimization
const PostCard = memo(({ post }: { post: Post }) => {
const [imageLoaded, setImageLoaded] = useState(false)
return VStack([
// User header
HStack([
Image({
src: post.author.avatar,
alt: post.author.name,
loading: 'lazy'
})
.modifier.frame(40, 40).cornerRadius(20).build(),
VStack([
Text(post.author.name).modifier.fontWeight('bold').build(),
Text(post.timestamp).modifier.fontSize(12).foregroundColor('#666').build()
]).modifier.flex(1).marginLeft(12).build()
]),
// Content
Text(post.content).modifier.marginVertical(12).build(),
// Media (if present)
post.image && VStack([
// Placeholder while loading
!imageLoaded && Spacer().modifier.height(200).backgroundColor('#f0f0f0').build(),
Image({
src: post.image,
alt: 'Post image',
loading: 'lazy'
})
.modifier
.aspectRatio(1.5, 'fill')
.cornerRadius(8)
.opacity(imageLoaded ? 1 : 0)
.onLoad(() => setImageLoaded(true))
.build()
]),
// Engagement
HStack([
Button({ title: '♥ Like', onTap: () => likePost(post.id) }),
Button({ title: '💬 Comment', onTap: () => showComments(post.id) }),
Button({ title: '🔄 Share', onTap: () => sharePost(post.id) })
]).modifier.marginTop(12).build()
])
.modifier
.padding(16)
.backgroundColor('#ffffff')
.cornerRadius(12)
.shadow({ x: 0, y: 2, blur: 4, color: 'rgba(0,0,0,0.1)' })
.build()
})
return LazyVGrid({
columns: [GridItem.adaptive(350, 500)], // Optimal for social content
spacing: 16,
animations: GridAnimations.slideIn('up', 200),
children: posts.map(post =>
PostCard({ post, key: post.id })
)
})
}3. Dashboard Widget Grid
Optimized for real-time updates:
const DashboardGrid = ({ widgets }: { widgets: Widget[] }) => {
// Memoize widgets to prevent unnecessary re-renders
const MemoizedWidget = memo(({ widget }: { widget: Widget }) => {
return VStack([
HStack([
Text(widget.title).modifier.fontWeight('bold').build(),
Spacer(),
widget.loading && LoadingSpinner().modifier.frame(16, 16).build()
]),
widget.data ?
WidgetContent({ data: widget.data, type: widget.type }) :
SkeletonLoader()
])
.modifier
.padding(16)
.backgroundColor('#ffffff')
.cornerRadius(8)
.shadow({ x: 0, y: 1, blur: 3, color: 'rgba(0,0,0,0.1)' })
.build()
})
return LazyVGrid({
columns: [
GridItem.adaptive(300, 400) // Flexible widget sizing
],
spacing: 16,
// Minimal animations for frequent updates
animations: GridAnimations.smoothLayout(150),
children: widgets.map(widget =>
MemoizedWidget({ widget, key: widget.id })
)
})
}Performance Testing
1. Load Testing
Test grid performance with various datasets:
// Performance test suite
const performanceTests = {
async testSmallDataset() {
const items = generateItems(50)
const start = performance.now()
const grid = LazyVGrid({
columns: [GridItem.adaptive(250)],
children: items.map(item => ItemComponent(item))
})
const end = performance.now()
console.log(`Small dataset (50 items): ${end - start}ms`)
},
async testLargeDataset() {
const items = generateItems(5000)
const start = performance.now()
const grid = LazyVGrid({
columns: [GridItem.adaptive(250)],
children: items.slice(0, 100).map(item => ItemComponent(item)) // Virtual scrolling
})
const end = performance.now()
console.log(`Large dataset (5000 items, 100 rendered): ${end - start}ms`)
},
async testComplexItems() {
const items = generateComplexItems(100)
const start = performance.now()
const grid = LazyVGrid({
columns: [GridItem.adaptive(300, 400)],
children: items.map(item => ComplexItemComponent(item))
})
const end = performance.now()
console.log(`Complex items (100 items): ${end - start}ms`)
}
}2. Memory Profiling
Monitor memory usage over time:
const ProfiledGrid = ({ items }) => {
useEffect(() => {
if (process.env.NODE_ENV === 'development') {
const memoryMonitor = setInterval(() => {
if (performance.memory) {
console.log('Memory usage:', {
used: Math.round(performance.memory.usedJSHeapSize / 1024 / 1024) + 'MB',
total: Math.round(performance.memory.totalJSHeapSize / 1024 / 1024) + 'MB',
limit: Math.round(performance.memory.jsHeapSizeLimit / 1024 / 1024) + 'MB'
})
}
}, 5000)
return () => clearInterval(memoryMonitor)
}
}, [])
return LazyVGrid({
columns: [GridItem.adaptive(250)],
children: items
})
}By following these performance best practices, your TachUI Grid components will deliver optimal performance across all devices and use cases, maintaining smooth 60fps interactions even with large datasets and complex layouts.