$npx -y skills add jmxt3/gitscape.ai --skill performance-optimizationOptimizes application performance. Use when performance requirements exist, when you suspect performance regressions, or when Core Web Vitals or load times need improvement. Use when profiling reveals bottlenecks that need fixing.
| 1 | # Performance Optimization |
| 2 | |
| 3 | ## Overview |
| 4 | |
| 5 | Measure first, then optimize. Never optimize without data. Performance problems have specific root causes — guessing wastes time and can introduce regressions. The goal is to identify the specific bottleneck, fix it, and measure the improvement. |
| 6 | |
| 7 | ## When to Use |
| 8 | |
| 9 | - A specific performance regression has been reported or measured |
| 10 | - A feature has a performance requirement (e.g., "< 200ms API response time") |
| 11 | - Core Web Vitals are in the "Needs Improvement" or "Poor" range |
| 12 | - Bundle size has grown significantly |
| 13 | |
| 14 | **When NOT to use:** "Let's make it faster" without a measured baseline. Profile first. |
| 15 | |
| 16 | ## The Measure-First Workflow |
| 17 | |
| 18 | ### Step 1: Establish a Baseline |
| 19 | |
| 20 | Before changing anything, capture the current state: |
| 21 | |
| 22 | ```bash |
| 23 | # API response time (via curl) |
| 24 | curl -w "@curl-format.txt" -s -o /dev/null https://api.gitscape.app/api/skills |
| 25 | |
| 26 | # Frontend bundle size |
| 27 | npx vite build --mode production 2>&1 | grep "dist/" |
| 28 | |
| 29 | # Lighthouse audit (frontend) |
| 30 | npx lighthouse https://gitscape.app --output json --output-path ./baseline.json |
| 31 | ``` |
| 32 | |
| 33 | Record the specific numbers. You need them to prove the optimization worked. |
| 34 | |
| 35 | ### Step 2: Profile to Find the Real Bottleneck |
| 36 | |
| 37 | Don't guess. Use profiling tools: |
| 38 | |
| 39 | **Backend (Python FastAPI):** |
| 40 | ```python |
| 41 | import cProfile |
| 42 | import pstats |
| 43 | |
| 44 | with cProfile.Profile() as pr: |
| 45 | result = await generate_skill(repo) |
| 46 | |
| 47 | stats = pstats.Stats(pr) |
| 48 | stats.sort_stats("cumulative") |
| 49 | stats.print_stats(20) # Top 20 hotspots |
| 50 | ``` |
| 51 | |
| 52 | **Frontend (React):** |
| 53 | - Chrome DevTools → Performance tab → Record → Replay user action |
| 54 | - React DevTools Profiler → Identify components that re-render unnecessarily |
| 55 | |
| 56 | **Network:** |
| 57 | - Chrome DevTools → Network tab → Filter by type, check response sizes and waterfall |
| 58 | |
| 59 | ### Step 3: Fix Only the Measured Bottleneck |
| 60 | |
| 61 | Fix the specific issue the profile reveals — not everything that could theoretically be faster. |
| 62 | |
| 63 | ### Step 4: Measure After |
| 64 | |
| 65 | Compare against the baseline. If the improvement isn't measurable, the optimization wasn't worth it. |
| 66 | |
| 67 | ## Common Bottlenecks in GitScape |
| 68 | |
| 69 | ### Large File Previews Freezing the Browser |
| 70 | |
| 71 | ```tsx |
| 72 | // BAD: Rendering full file content for large files |
| 73 | function FilePreview({ content }: { content: string }) { |
| 74 | return <pre>{content}</pre>; // 10MB file = browser freeze |
| 75 | } |
| 76 | |
| 77 | // GOOD: Truncate at a safe limit |
| 78 | const MAX_PREVIEW_CHARS = 10_000; |
| 79 | |
| 80 | function FilePreview({ content }: { content: string }) { |
| 81 | const truncated = content.length > MAX_PREVIEW_CHARS; |
| 82 | return ( |
| 83 | <div> |
| 84 | <pre>{truncated ? content.slice(0, MAX_PREVIEW_CHARS) : content}</pre> |
| 85 | {truncated && <p>Preview truncated — full content exported.</p>} |
| 86 | </div> |
| 87 | ); |
| 88 | } |
| 89 | ``` |
| 90 | |
| 91 | ### Slow GitHub API Calls (N+1 Pattern) |
| 92 | |
| 93 | ```python |
| 94 | # BAD: Separate API call per file (N+1) |
| 95 | async def get_file_contents(repo: str, paths: list[str]) -> dict: |
| 96 | result = {} |
| 97 | for path in paths: |
| 98 | # One API call per file |
| 99 | result[path] = await github.get_file(repo, path) |
| 100 | return result |
| 101 | |
| 102 | # GOOD: Batch or parallelize |
| 103 | async def get_file_contents(repo: str, paths: list[str]) -> dict: |
| 104 | tasks = [github.get_file(repo, path) for path in paths] |
| 105 | contents = await asyncio.gather(*tasks, return_exceptions=True) |
| 106 | return {path: content for path, content in zip(paths, contents) |
| 107 | if not isinstance(content, Exception)} |
| 108 | ``` |
| 109 | |
| 110 | ### Unnecessary React Re-renders |
| 111 | |
| 112 | ```tsx |
| 113 | // BAD: New object reference on every render |
| 114 | function SkillList({ skills }: Props) { |
| 115 | return <SkillFilters options={{ sortBy: 'name' }} />; |
| 116 | } |
| 117 | |
| 118 | // GOOD: Stable reference |
| 119 | const DEFAULT_FILTER_OPTIONS = { sortBy: 'name' } as const; |
| 120 | function SkillList({ skills }: Props) { |
| 121 | return <SkillFilters options={DEFAULT_FILTER_OPTIONS} />; |
| 122 | } |
| 123 | ``` |
| 124 | |
| 125 | ### Large Bundle Size |
| 126 | |
| 127 | ```typescript |
| 128 | // GOOD: Dynamic import for heavy, rarely-used features |
| 129 | const MonacoEditor = lazy(() => import('./MonacoEditor')); |
| 130 | |
| 131 | function App() { |
| 132 | return ( |
| 133 | <Suspense fallback={<div>Loading editor...</div>}> |
| 134 | <MonacoEditor /> |
| 135 | </Suspense> |
| 136 | ); |
| 137 | } |
| 138 | ``` |
| 139 | |
| 140 | ## Performance Budget |
| 141 | |
| 142 | Track these targets and alert when exceeded: |
| 143 | |
| 144 | ``` |
| 145 | API response time (p95): < 2000ms for skill generation |
| 146 | < 200ms for metadata endpoints |
| 147 | Frontend JS bundle: < 200KB gzipped (initial load) |
| 148 | File preview rendering: < 100ms for files < 10KB |
| 149 | Lighthouse Performance: ≥ 80 (acceptable), ≥ 90 (target) |
| 150 | ``` |
| 151 | |
| 152 | ## Common Rationalizations |
| 153 | |
| 154 | | Rationalization | Reality | |
| 155 | |---|---| |
| 156 | | "We'll optimize later" | Performance debt compounds. Address measured regressions now. | |
| 157 | | "It's fast on my machine" | Your machine isn't the user's. Profile on representative hardware. | |
| 158 | | "This optimization is obvious" | If you didn't measure, you don't know. Profile first. | |
| 159 | | "The framework handles performance" | Fra |