Skip to main content

Performance Optimization Guide

Overview

This document outlines the performance optimizations implemented in Dashtray to ensure fast load times, smooth interactions, and efficient resource usage.

Bundle Optimization

Code Splitting

The application uses manual chunk splitting to optimize caching:
  • vendor-svelte: Core Svelte runtime
  • vendor-convex: Convex client and bindings
  • vendor-ui: UI component libraries (bits-ui, vaul-svelte, svelte-sonner)
  • vendor-charts: Chart libraries (layerchart, d3-scale, d3-shape)
  • vendor-dnd: Drag-and-drop libraries

Build Configuration

// vite.config.ts
build: {
  rollupOptions: {
    output: {
      manualChunks: {
        'vendor-svelte': ['svelte', 'svelte/store'],
        'vendor-convex': ['convex', 'convex-svelte'],
        // ... other chunks
      }
    }
  },
  chunkSizeWarningLimit: 600,
  minify: 'esbuild',
  sourcemap: false,
  cssCodeSplit: true,
  target: 'es2020'
}

Lazy Loading

Components and routes are lazy-loaded using dynamic imports:
// Example: Lazy load heavy components
const HeavyChart = lazyLoad(() => import('./HeavyChart.svelte'));

Database Query Optimization

Indexes

All frequently queried fields have indexes:
  • projects: by_slug, by_owner, by_subscription_tier, by_subscription_status
  • metrics: by_project, by_connection, by_timestamp, by_project_and_metric, by_project_and_timestamp
  • dashboards: by_project, by_slug, by_type, by_project_and_type
  • connections: by_project, by_service, by_status, by_project_and_service

Query Patterns

  1. Use compound indexes for multi-field queries
  2. Paginate large result sets (limit + cursor)
  3. Aggregate at query time instead of fetching all data
  4. Cache frequently accessed data (5-minute TTL)

Example Optimized Query

// Bad: Fetch all metrics then filter in memory
const allMetrics = await ctx.db.query('metrics').collect();
const filtered = allMetrics.filter(m => m.projectId === projectId);

// Good: Use index to filter at database level
const metrics = await ctx.db
  .query('metrics')
  .withIndex('by_project', q => q.eq('projectId', projectId))
  .take(100);

Caching Strategy

Client-Side Caching

The queryCache utility provides in-memory caching:
import { queryCache, cacheKeys } from '$lib/utils/query-cache';

// Check cache first
const cached = queryCache.get(cacheKeys.metrics(projectId, metricId, timeRange));
if (cached) return cached;

// Fetch and cache
const data = await fetchMetrics();
queryCache.set(cacheKeys.metrics(projectId, metricId, timeRange), data, 5 * 60 * 1000);

Cache Invalidation

Invalidate cache when data changes:
// Invalidate specific key
queryCache.invalidate(cacheKeys.dashboard(dashboardId));

// Invalidate pattern
queryCache.invalidatePattern(invalidationPatterns.metrics(projectId));

Cache TTLs

  • Metrics: 5 minutes
  • Dashboards: 10 minutes
  • Projects: 15 minutes
  • Connections: 5 minutes

Image Optimization

OptimizedImage Component

Use the OptimizedImage component for automatic lazy loading:
<OptimizedImage
  src="/logo.png"
  alt="Logo"
  width={200}
  height={100}
  loading="lazy"
/>

Features

  • Lazy loading with Intersection Observer
  • Loading skeleton
  • Error handling
  • Priority loading for above-the-fold images

Best Practices

  1. Always specify width and height to prevent layout shift
  2. Use priority={true} for above-the-fold images
  3. Use WebP/AVIF formats when possible
  4. Compress images before upload (max 2MB)

Performance Utilities

Debounce and Throttle

import { debounce, throttle } from '$lib/utils/performance-optimizations';

// Debounce search input
const handleSearch = debounce((query: string) => {
  // Search logic
}, 300);

// Throttle scroll handler
const handleScroll = throttle(() => {
  // Scroll logic
}, 100);

Performance Measurement

import { measurePerformance } from '$lib/utils/performance-optimizations';

const data = await measurePerformance('fetchDashboard', async () => {
  return await fetchDashboard(dashboardId);
});
// Console: [Performance] fetchDashboard: 123.45ms

Adaptive Loading

import { shouldLoadHeavyContent } from '$lib/utils/performance-optimizations';

if (shouldLoadHeavyContent()) {
  // Load high-quality images and animations
} else {
  // Load lightweight alternatives
}

Network Optimization

Request Batching

Batch multiple requests into a single query:
// Bad: Multiple sequential requests
const project = await getProject(projectId);
const connections = await getConnections(projectId);
const dashboards = await getDashboards(projectId);

// Good: Single request with all data
const data = await getProjectData(projectId); // Returns { project, connections, dashboards }

Prefetching

Prefetch routes and data on hover:
import { preloadRoute } from '$lib/utils/performance-optimizations';

<a href="/dashboard" onmouseenter={() => preloadRoute('/dashboard')}>
  Dashboard
</a>

Rendering Optimization

Virtual Scrolling

For large lists (>100 items), use virtual scrolling:
<script>
  import VirtualList from '$lib/components/virtual-list.svelte';
</script>

<VirtualList items={metrics} itemHeight={60} let:item>
  <MetricRow metric={item} />
</VirtualList>

Reduced Motion

Respect user preferences:
import { prefersReducedMotion } from '$lib/utils/performance-optimizations';

const shouldAnimate = !prefersReducedMotion();

Monitoring

Core Web Vitals

Monitor these metrics:
  • LCP (Largest Contentful Paint): < 2.5s
  • FID (First Input Delay): < 100ms
  • CLS (Cumulative Layout Shift): < 0.1

Performance Budget

  • Initial bundle: < 200KB (gzipped)
  • Total page weight: < 1MB
  • Time to Interactive: < 3s
  • API response time: < 200ms

Monitoring Tools

  • Sentry for error tracking and performance monitoring
  • Mixpanel for usage analytics
  • Cloudflare Analytics for CDN performance

Testing Performance

Lighthouse

Run Lighthouse audits regularly:
# Install Lighthouse
npm install -g lighthouse

# Run audit
lighthouse https://dashtray.com --view

Slow Connection Testing

Test on slow connections:
# Chrome DevTools: Network tab > Throttling > Slow 3G

Bundle Analysis

Analyze bundle size:
# Build with analysis
pnpm build

# Check .svelte-kit/cloudflare/_app/immutable/chunks/

Checklist

  • Bundle size < 200KB (gzipped)
  • All images lazy-loaded
  • Database queries use indexes
  • Caching implemented for frequently accessed data
  • Virtual scrolling for large lists
  • Debounce/throttle expensive operations
  • Prefetch critical routes
  • Code splitting for large dependencies
  • Lighthouse score > 90
  • Core Web Vitals in green

Resources