// "Comprehensive performance analysis and optimization for Justice Companion: React rendering, SQLite query performance, bundle size analysis, memory leak detection. Use when debugging slow UI, optimizing database queries, or reducing app footprint."
| name | performance-optimization |
| description | Comprehensive performance analysis and optimization for Justice Companion: React rendering, SQLite query performance, bundle size analysis, memory leak detection. Use when debugging slow UI, optimizing database queries, or reducing app footprint. |
| allowed-tools | ["Bash","Read","Write","Edit","Grep","Glob","mcp__sqlite__*"] |
Identify and fix performance bottlenecks across React UI, SQLite database, and Electron build.
| Metric | Target | Critical Threshold |
|---|---|---|
| Initial Load | < 2s | < 3s |
| Route Change | < 100ms | < 300ms |
| DB Query (simple) | < 10ms | < 50ms |
| DB Query (complex) | < 50ms | < 200ms |
| Bundle Size (gzip) | < 5MB | < 10MB |
| Memory Usage | < 200MB | < 500MB |
| React Render | < 16ms (60fps) | < 33ms (30fps) |
# Run React DevTools Profiler
pnpm dev
# Open http://localhost:5176
# React DevTools โ Profiler โ Record
Issue: Unnecessary Re-renders
// โ BAD: Creates new object every render
<Component data={{ id: 1, name: 'Test' }} />
// โ
GOOD: Memoize objects
const data = useMemo(() => ({ id: 1, name: 'Test' }), []);
<Component data={data} />
Issue: Large Lists Without Virtualization
// โ BAD: Renders all 10,000 items
{cases.map(case => <CaseCard key={case.id} {...case} />)}
// โ
GOOD: Use react-window or @tanstack/react-virtual
import { useVirtualizer } from '@tanstack/react-virtual';
Issue: Context Updates Trigger All Consumers
// โ BAD: Single large context
<AppContext.Provider value={{ user, cases, settings }} />
// โ
GOOD: Split contexts
<UserContext.Provider value={user}>
<CasesContext.Provider value={cases}>
<SettingsContext.Provider value={settings}>
React.memo()useMemo() for expensive calculationsuseCallback() for event handlers passed to childrenReact.lazy() for code splitting# Enable query logging
node -e "
const db = require('better-sqlite3')('justice.db');
db.pragma('journal_mode = WAL');
console.time('Query');
const result = db.prepare('SELECT * FROM cases').all();
console.timeEnd('Query');
"
Add Missing Indexes
-- Check query plan
EXPLAIN QUERY PLAN
SELECT * FROM cases WHERE user_id = ? AND status = 'open';
-- Add composite index if missing
CREATE INDEX IF NOT EXISTS idx_cases_user_status
ON cases(user_id, status);
Use Prepared Statements (Already Cached)
// โ
Drizzle ORM does this automatically
const cases = await db.select().from(cases).where(eq(cases.userId, userId));
Enable WAL Mode (Write-Ahead Logging)
// In src/db/database.ts
db.pragma('journal_mode = WAL');
db.pragma('synchronous = NORMAL');
db.pragma('cache_size = -64000'); // 64MB cache
Batch Inserts
// โ BAD: Individual inserts
for (const item of items) {
await db.insert(table).values(item);
}
// โ
GOOD: Single batch insert
await db.insert(table).values(items);
-- Cases table
CREATE INDEX idx_cases_user_id ON cases(user_id);
CREATE INDEX idx_cases_status ON cases(status);
CREATE INDEX idx_cases_created_at ON cases(created_at);
-- Evidence table
CREATE INDEX idx_evidence_case_id ON evidence(case_id);
CREATE INDEX idx_evidence_type ON evidence(evidence_type);
-- Audit logs
CREATE INDEX idx_audit_timestamp ON audit_logs(timestamp);
CREATE INDEX idx_audit_user_action ON audit_logs(user_id, action);
# Build and analyze
pnpm build
pnpm exec vite-bundle-visualizer
# Check gzipped sizes
du -sh dist/**/*.js | sort -h
moment โ date-fns (saves ~70KB)lodash โ lodash-es (tree-shakeable)build.rollupOptions in vite.config.tsExample: Dynamic Imports
// โ BAD: Import heavy library upfront
import html2pdf from 'html2pdf.js';
// โ
GOOD: Dynamic import on demand
const exportPDF = async () => {
const html2pdf = await import('html2pdf.js');
// Use html2pdf
};
# Chrome DevTools Memory Profiler
# Electron โ Developer Tools โ Memory โ Take Heap Snapshot
# Or use process.memoryUsage()
node -e "console.log(process.memoryUsage())"
Event Listeners Not Cleaned Up
// โ BAD: Listener never removed
useEffect(() => {
window.addEventListener('resize', handleResize);
}, []);
// โ
GOOD: Cleanup in effect return
useEffect(() => {
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
Database Connections Not Closed
// โ
Better-sqlite3 uses single persistent connection
// But close on app exit:
app.on('before-quit', () => {
db.close();
});
React Query Cache Growing Indefinitely
// Set cache time in QueryClient
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5, // 5 minutes
cacheTime: 1000 * 60 * 30, // 30 minutes
},
},
});
# React component rendering
pnpm benchmark:pagination
# Database queries
tsx src/benchmarks/db-benchmark.ts
# Bundle analysis
pnpm build && ls -lh dist/**/*.js
// src/benchmarks/example-benchmark.ts
import { performance } from 'perf_hooks';
const iterations = 1000;
const start = performance.now();
for (let i = 0; i < iterations; i++) {
// Operation to benchmark
}
const end = performance.now();
console.log(`Avg: ${(end - start) / iterations}ms`);
// src/utils/performance.ts
export const trackMetric = (name: string, value: number) => {
if (value > THRESHOLDS[name]) {
console.warn(`โ ๏ธ Performance: ${name} = ${value}ms (threshold: ${THRESHOLDS[name]}ms)`);
}
};
// Usage
const start = performance.now();
const result = await expensiveOperation();
trackMetric('expensiveOperation', performance.now() - start);
# Profile React components
pnpm dev
# Then use React DevTools Profiler
# Analyze SQLite queries
sqlite3 justice.db "EXPLAIN QUERY PLAN SELECT * FROM cases;"
# Bundle size analysis
pnpm build
pnpm exec vite-bundle-visualizer
# Memory profiling
node --inspect electron/main.ts
# Open chrome://inspect
# Check all indexes
sqlite3 justice.db "SELECT * FROM sqlite_master WHERE type='index';"