Enforce Production-Grade Engineering Workflows for AI Agents
A measure-first performance workflow covering Core Web Vitals, bottleneck diagnosis, and fixes for N+1 queries, images, and re-renders.
Why it matters
Ensure AI coding agents follow senior engineer workflows across the entire software development lifecycle-from spec writing and planning through implementation, testing, review, and deployment-with built-in quality gates and best practices at every phase.
Outcomes
What it gets done
Break down vague ideas into concrete specs with structured interviews and PRDs before writing code
Decompose features into small, testable tasks with acceptance criteria and dependency ordering
Implement changes incrementally with test-driven development, feature flags, and safe rollback patterns
Review code for quality, performance, and security before merging or shipping to production
Install
Add it to your toolbox
Run in your project directory:
curl -fsSL https://spark.entire.vc/get/ag-performance-optimization | bash Overview
Performance Optimization
This skill runs a measure-first performance workflow (measure, identify, fix, verify, guard) against Core Web Vitals targets, diagnosing bottlenecks via a symptom decision tree and fixing common anti-patterns like N+1 queries, unoptimized images, unnecessary re-renders, and missing caching. Use it when there's evidence of a performance problem - a spec'd budget, monitoring alert, failing Core Web Vitals, suspected regression, or a large-data/high-traffic feature. Not for optimizing without measured evidence of a problem.
What it does
A measure-first performance optimization process built on the rule that performance work without measurement is guessing, and premature optimization adds complexity without improving what matters. The five-step workflow is Measure (establish a baseline), Identify (find the actual bottleneck, not the assumed one), Fix (address that specific bottleneck), Verify (measure again to confirm improvement), and Guard (add monitoring or tests against regression). Measurement uses two complementary approaches: synthetic tools (Lighthouse, Chrome DevTools Performance tab, Chrome DevTools MCP) for controlled, reproducible CI regression detection, and RUM (the web-vitals library, CrUX) for real user data that's required to validate a fix actually helped. Core Web Vitals targets are explicit - LCP good at 2.5s or under (poor above 4.0s), INP good at 200ms or under (poor above 500ms), CLS good at 0.1 or under (poor above 0.25) - and a symptom-driven decision tree routes "what is slow" (first page load, sluggish interaction, post-navigation rendering, or backend/API) down to the specific thing to measure next, like TTFB in the network waterfall or main-thread long tasks over 50ms. Common bottlenecks are tabulated by symptom-cause-investigation for both frontend (slow LCP, high CLS, poor INP, slow initial load) and backend (slow API responses, memory growth, CPU spikes, high latency). Six anti-patterns get concrete before/after fixes: N+1 queries replaced with a single joined/included query, unbounded data fetching replaced with paginated take/skip queries, missing image optimization replaced with responsive <picture> markup (art direction plus resolution switching, AVIF/WebP sources, fetchpriority="high" for the LCP image, loading="lazy" for below-the-fold images), unnecessary React re-renders fixed with stable object references, React.memo, and useMemo, large bundle size addressed with dynamic imports and route-level code splitting rather than changing import styles before profiling, and missing caching addressed with an in-memory TTL cache for frequently-read data plus HTTP Cache-Control headers for static assets and API responses.
const tasks = await db.tasks.findMany();
for (const task of tasks) {
task.owner = await db.users.findUnique({ where: { id: task.ownerId } });
}
versus a single query with a join:
const tasks = await db.tasks.findMany({
include: { owner: true },
});
A performance budget sets concrete enforceable numbers (JS bundle under 200KB gzipped, CSS under 50KB, above-the-fold images under 200KB each, API p95 under 200ms, Time to Interactive under 3.5s on 4G, Lighthouse score 90+), checked in CI via bundlesize and lhci autorun. It flags common rationalizations as wrong ("we'll optimize later" lets debt compound; "it's fast on my machine" ignores that the user's machine and network aren't yours; "users won't notice 100ms" contradicts research on conversion impact) and lists red flags including optimization without profiling data, unpaginated list endpoints, and overusing React.memo/useMemo as badly as underusing them.
When to use - and when NOT to
Use it when performance requirements exist in the spec, users or monitoring report slow behavior, Core Web Vitals scores are below threshold, a change may have introduced a regression, or a feature handles large datasets or high traffic. Do NOT use it to optimize before there's evidence of a problem - premature optimization adds complexity that costs more than the performance it gains.
Inputs and outputs
Input is a performance symptom or requirement (a specific slow flow, a failing Core Web Vitals score, a suspected regression). Output is a before/after measurement pair, the identified bottleneck, a targeted fix for the specific anti-pattern found, and a regression guard (monitoring or a CI budget check).
Integrations
Uses Lighthouse, Chrome DevTools (including DevTools MCP), the web-vitals library, and CrUX for measurement, plus CI tools like bundlesize and Lighthouse CI (lhci autorun) to enforce a performance budget automatically.
Who it's for
Developers responding to a real, measured performance problem - a Core Web Vitals regression, a slow API, a reported slowdown - who want a disciplined measure-identify-fix-verify-guard process and concrete fixes for the most common frontend and backend anti-patterns, rather than guessing at optimizations.
Source README
Performance Optimization
Overview
Measure before optimizing. Performance work without measurement is guessing - and guessing leads to premature optimization that adds complexity without improving what matters. Profile first, identify the actual bottleneck, fix it, measure again. Optimize only what measurements prove matters.
When to Use
- Performance requirements exist in the spec (load time budgets, response time SLAs)
- Users or monitoring report slow behavior
- Core Web Vitals scores are below thresholds
- You suspect a change introduced a regression
- Building features that handle large datasets or high traffic
When NOT to use: Don't optimize before you have evidence of a problem. Premature optimization adds complexity that costs more than the performance it gains.
Core Web Vitals Targets
| Metric | Good | Needs Improvement | Poor |
|---|---|---|---|
| LCP (Largest Contentful Paint) | ≤ 2.5s | ≤ 4.0s | > 4.0s |
| INP (Interaction to Next Paint) | ≤ 200ms | ≤ 500ms | > 500ms |
| CLS (Cumulative Layout Shift) | ≤ 0.1 | ≤ 0.25 | > 0.25 |
The Optimization Workflow
1. MEASURE → Establish baseline with real data
2. IDENTIFY → Find the actual bottleneck (not assumed)
3. FIX → Address the specific bottleneck
4. VERIFY → Measure again, confirm improvement
5. GUARD → Add monitoring or tests to prevent regression
Step 1: Measure
Two complementary approaches - use both:
- Synthetic (Lighthouse, DevTools Performance tab): Controlled conditions, reproducible. Best for CI regression detection and isolating specific issues.
- RUM (web-vitals library, CrUX): Real user data in real conditions. Required to validate that a fix actually improved user experience.
Frontend:
### Synthetic: Lighthouse in Chrome DevTools (or CI)
### Chrome DevTools → Performance tab → Record
### Chrome DevTools MCP → Performance trace
### RUM: Web Vitals library in code
import { onLCP, onINP, onCLS } from 'web-vitals';
onLCP(console.log);
onINP(console.log);
onCLS(console.log);
Backend:
### Response time logging
### Application Performance Monitoring (APM)
### Database query logging with timing
### Simple timing
console.time('db-query');
const result = await db.query(...);
console.timeEnd('db-query');
Where to Start Measuring
Use the symptom to decide what to measure first:
What is slow?
├── First page load
│ ├── Large bundle? --> Measure bundle size, check code splitting
│ ├── Slow server response? --> Measure TTFB in DevTools Network waterfall
│ │ ├── DNS long? --> Add dns-prefetch / preconnect for known origins
│ │ ├── TCP/TLS long? --> Enable HTTP/2, check edge deployment, keep-alive
│ │ └── Waiting (server) long? --> Profile backend, check queries and caching
│ └── Render-blocking resources? --> Check network waterfall for CSS/JS blocking
├── Interaction feels sluggish
│ ├── UI freezes on click? --> Profile main thread, look for long tasks (>50ms)
│ ├── Form input lag? --> Check re-renders, controlled component overhead
│ └── Animation jank? --> Check layout thrashing, forced reflows
├── Page after navigation
│ ├── Data loading? --> Measure API response times, check for waterfalls
│ └── Client rendering? --> Profile component render time, check for N+1 fetches
└── Backend / API
├── Single endpoint slow? --> Profile database queries, check indexes
├── All endpoints slow? --> Check connection pool, memory, CPU
└── Intermittent slowness? --> Check for lock contention, GC pauses, external deps
Step 2: Identify the Bottleneck
Common bottlenecks by category:
Frontend:
| Symptom | Likely Cause | Investigation |
|---|---|---|
| Slow LCP | Large images, render-blocking resources, slow server | Check network waterfall, image sizes |
| High CLS | Images without dimensions, late-loading content, font shifts | Check layout shift attribution |
| Poor INP | Heavy JavaScript on main thread, large DOM updates | Check long tasks in Performance trace |
| Slow initial load | Large bundle, many network requests | Check bundle size, code splitting |
Backend:
| Symptom | Likely Cause | Investigation |
|---|---|---|
| Slow API responses | N+1 queries, missing indexes, unoptimized queries | Check database query log |
| Memory growth | Leaked references, unbounded caches, large payloads | Heap snapshot analysis |
| CPU spikes | Synchronous heavy computation, regex backtracking | CPU profiling |
| High latency | Missing caching, redundant computation, network hops | Trace requests through the stack |
Step 3: Fix Common Anti-Patterns
N+1 Queries (Backend)
// BAD: N+1 — one query per task for the owner
const tasks = await db.tasks.findMany();
for (const task of tasks) {
task.owner = await db.users.findUnique({ where: { id: task.ownerId } });
}
// GOOD: Single query with join/include
const tasks = await db.tasks.findMany({
include: { owner: true },
});
Unbounded Data Fetching
// BAD: Fetching all records
const allTasks = await db.tasks.findMany();
// GOOD: Paginated with limits
const tasks = await db.tasks.findMany({
take: 20,
skip: (page - 1) * 20,
orderBy: { createdAt: 'desc' },
});
Missing Image Optimization (Frontend)
<img src="/hero.jpg" />
<picture>
<source
media="(max-width: 767px)"
srcset="/hero-mobile-400.avif 400w, /hero-mobile-800.avif 800w"
sizes="100vw"
width="800"
height="1000"
type="image/avif"
/>
<source
media="(max-width: 767px)"
srcset="/hero-mobile-400.webp 400w, /hero-mobile-800.webp 800w"
sizes="100vw"
width="800"
height="1000"
type="image/webp"
/>
<source
srcset="/hero-800.avif 800w, /hero-1200.avif 1200w, /hero-1600.avif 1600w"
sizes="(max-width: 1200px) 100vw, 1200px"
width="1200"
height="600"
type="image/avif"
/>
<source
srcset="/hero-800.webp 800w, /hero-1200.webp 1200w, /hero-1600.webp 1600w"
sizes="(max-width: 1200px) 100vw, 1200px"
width="1200"
height="600"
type="image/webp"
/>
<img
src="/hero-desktop.jpg"
width="1200"
height="600"
fetchpriority="high"
alt="Hero image description"
/>
</picture>
<img
src="/content.webp"
width="800"
height="400"
loading="lazy"
decoding="async"
alt="Content image description"
/>
Unnecessary Re-renders (React)
// BAD: Creates new object on every render, causing children to re-render
function TaskList() {
return <TaskFilters options={{ sortBy: 'date', order: 'desc' }} />;
}
// GOOD: Stable reference
const DEFAULT_OPTIONS = { sortBy: 'date', order: 'desc' } as const;
function TaskList() {
return <TaskFilters options={DEFAULT_OPTIONS} />;
}
// Use React.memo for expensive components
const TaskItem = React.memo(function TaskItem({ task }: Props) {
return <div>{/* expensive render */}</div>;
});
// Use useMemo for expensive computations
function TaskStats({ tasks }: Props) {
const stats = useMemo(() => calculateStats(tasks), [tasks]);
return <div>{stats.completed} / {stats.total}</div>;
}
Large Bundle Size
// Modern bundlers (Vite, webpack 5+) handle named imports with tree-shaking automatically,
// provided the dependency ships ESM and is marked `sideEffects: false` in package.json.
// Profile before changing import styles — the real gains come from splitting and lazy loading.
// GOOD: Dynamic import for heavy, rarely-used features
const ChartLibrary = lazy(() => import('./ChartLibrary'));
// GOOD: Route-level code splitting wrapped in Suspense
const SettingsPage = lazy(() => import('./pages/Settings'));
function App() {
return (
<Suspense fallback={<Spinner />}>
<SettingsPage />
</Suspense>
);
}
Missing Caching (Backend)
// Cache frequently-read, rarely-changed data
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
let cachedConfig: AppConfig | null = null;
let cacheExpiry = 0;
async function getAppConfig(): Promise<AppConfig> {
if (cachedConfig && Date.now() < cacheExpiry) {
return cachedConfig;
}
cachedConfig = await db.config.findFirst();
cacheExpiry = Date.now() + CACHE_TTL;
return cachedConfig;
}
// HTTP caching headers for static assets
app.use('/static', express.static('public', {
maxAge: '1y', // Cache for 1 year
immutable: true, // Never revalidate (use content hashing in filenames)
}));
// Cache-Control for API responses
res.set('Cache-Control', 'public, max-age=300'); // 5 minutes
Performance Budget
Set budgets and enforce them:
JavaScript bundle: < 200KB gzipped (initial load)
CSS: < 50KB gzipped
Images: < 200KB per image (above the fold)
Fonts: < 100KB total
API response time: < 200ms (p95)
Time to Interactive: < 3.5s on 4G
Lighthouse Performance score: ≥ 90
Enforce in CI:
### Bundle size check
npx bundlesize --config bundlesize.config.json
### Lighthouse CI
npx lhci autorun
See Also
For detailed performance checklists, optimization commands, and anti-pattern reference, see references/performance-checklist.md.
Common Rationalizations
| Rationalization | Reality |
|---|---|
| "We'll optimize later" | Performance debt compounds. Fix obvious anti-patterns now, defer micro-optimizations. |
| "It's fast on my machine" | Your machine isn't the user's. Profile on representative hardware and networks. |
| "This optimization is obvious" | If you didn't measure, you don't know. Profile first. |
| "Users won't notice 100ms" | Research shows 100ms delays impact conversion rates. Users notice more than you think. |
| "The framework handles performance" | Frameworks prevent some issues but can't fix N+1 queries or oversized bundles. |
Red Flags
- Optimization without profiling data to justify it
- N+1 query patterns in data fetching
- List endpoints without pagination
- Images without dimensions, lazy loading, or responsive sizes
- Bundle size growing without review
- No performance monitoring in production
React.memoanduseMemoeverywhere (overusing is as bad as underusing)
Verification
After any performance-related change:
- Before and after measurements exist (specific numbers)
- The specific bottleneck is identified and addressed
- Core Web Vitals are within "Good" thresholds
- Bundle size hasn't increased significantly
- No N+1 queries in new data fetching code
- Performance budget passes in CI (if configured)
- Existing tests still pass (optimization didn't break behavior)
Limitations
- Use this skill only when the task clearly matches its upstream source and local project context.
- Verify commands, generated code, dependencies, credentials, and external service behavior before applying changes.
- Do not treat examples as a substitute for environment-specific tests, security review, or user approval for destructive or costly actions.
FAQ
Common questions
Discussion
Questions & comments · 0
Sign In Sign in to leave a comment.