/performance-audit — Bundle & Core Web Vitals Analysis
Deep-dive performance analysis focused on what's measurable from the codebase without running Lighthouse. Checks bundle size, image optimization, lazy loading, code splitting, and caching configuration. Invoke when /launch-check's performance row shows WARN or FAIL.
Process
Step 1: Bundle analysis
- Check for build output (
dist/, build/, .next/, out/)
- If build exists: measure total JS + CSS size, identify the 5 largest files
- Check for code splitting (dynamic imports,
React.lazy, next/dynamic, route-based splitting)
- Check for tree shaking configuration (ES modules,
sideEffects: false in package.json)
- Check if source maps are excluded from production build
Step 2: Image optimization
- Find all images in the project (src, public, static, assets directories)
- Check formats: are images served as WebP/AVIF, or only PNG/JPEG?
- Check for oversized images (> 500KB, > 2000px width)
- Check for
next/image, @astrojs/image, or similar optimization components
- Check for
loading="lazy" on below-the-fold images
- Check for explicit
width and height attributes (prevents layout shift)
Step 3: Loading performance
- Check for render-blocking resources (CSS in
<head> without media, sync <script> without defer/async)
- Check for font loading strategy (
font-display: swap, preloading, self-hosted vs Google Fonts)
- Check for preconnect/prefetch hints for critical third-party origins
- Check for service worker or caching configuration
Step 4: API performance (if applicable)
- Check for N+1 query patterns (multiple sequential fetches that could be batched)
- Check for pagination on list endpoints
- Check for caching headers (Cache-Control, ETag)
- Check for compression middleware (gzip/brotli)
Step 5: Output
PERFORMANCE AUDIT — <project> @ <sha>
| # | Area | Status | Finding |
|----|------|--------|---------|
| P1 | Bundle | WARN | Total JS 420KB gzipped (target: < 300KB). Largest: vendor.js 180KB |
| P2 | Code splitting | PASS | 12 dynamic imports, route-based splitting configured |
| P3 | Images | FAIL | 8 images > 500KB, none in WebP format |
| P4 | Lazy loading | WARN | 4 below-fold images without loading="lazy" |
| P5 | Fonts | PASS | Self-hosted, font-display: swap |
| P6 | Caching | PASS | Cache-Control headers on static assets |
Performance readiness: NEEDS WORK (1 fail, 2 warnings)
Estimated LCP improvement: -1.2s if images are optimized and lazy-loaded
Persist the run + render trend
After printing the findings table, persist via the shared audit-history lib so the performance trend across runs becomes legible. See docs/agdr/AgDR-0019-audit-artefact-persistence.md.
Resolve project name + score + verdict
<project-name> from apexyard.projects.yaml (or basename + /handover reminder if unregistered).
Score: score = max(0, 100 - 25*critical - 10*high - 3*medium - 1*low). Verdict by worst-severity: critical/high → fail, medium → conditional, low/none → pass. Legacy "Performance readiness" three-state: NEEDS WORK → fail/conditional (judgement call by counts), GOOD → pass.
Persist + render
source "$(git rev-parse --show-toplevel)/.claude/hooks/_lib-audit-history.sh"
# Lowercase severity in the payload — the lib expects critical/high/medium/low/info.
payload=$(mktemp); cat > "$payload" <<'EOF'
{
"schema_version": 1,
"findings": [
{"id": "P1", "severity": "high", "status": "open", "summary": "JS bundle 380KB gzipped (budget 250KB)"},
{"id": "P3", "severity": "medium", "status": "open", "summary": "loading=lazy missing on 14 below-fold images"},
{"id": "P5", "severity": "high", "status": "open", "summary": "Cache-Control no-cache on static assets — misses CDN edge caching"}
]
}
EOF
# Body: per templates/audits/performance-audit.md
body=$(mktemp); cat > "$body" <<'EOF'
... (filled-in body — findings table + Recommended priority + estimated impact) ...
EOF
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
audit_run_persist "<project-name>" "performance-audit" "$ts" "fail" 60 "$body" < "$payload"
rm -f "$payload" "$body"
audit_render_trend "<project-name>" "performance-audit" 5
Opt-in commit
touch projects/<name>/audits/performance-audit/.audit-history-tracked
Rules
- Auto-PASS for non-web projects. APIs, CLIs, libraries measure performance differently.
- Measure what you can from code. This skill reads source files and build output — it doesn't run Lighthouse. Suggest running Lighthouse separately for runtime metrics.
- Give specific file names and sizes for the largest offenders, not just "bundle is too big."
- Estimate impact where possible ("optimizing these 3 images would save ~800KB and improve LCP by ~1s").
- Always persist via the lib. The persist step runs regardless of opt-in commit state.
- Severity vocabulary in the JSON is lowercase. The lib expects
critical/high/medium/low/info.
1---2name: performance-audit-23description: Performance audit — bundle size, image opt, lazy load, code split, caching, CWV. Deep-dive for /launch-check performance.4---56# /performance-audit — Bundle & Core Web Vitals Analysis78Deep-dive performance analysis focused on what's measurable from the codebase without running Lighthouse. Checks bundle size, image optimization, lazy loading, code splitting, and caching configuration. Invoke when `/launch-check`'s performance row shows WARN or FAIL.910## Process1112### Step 1: Bundle analysis1314- Check for build output (`dist/`, `build/`, `.next/`, `out/`)15- If build exists: measure total JS + CSS size, identify the 5 largest files16- Check for code splitting (dynamic imports, `React.lazy`, `next/dynamic`, route-based splitting)17- Check for tree shaking configuration (ES modules, `sideEffects: false` in package.json)18- Check if source maps are excluded from production build1920### Step 2: Image optimization2122- Find all images in the project (src, public, static, assets directories)23- Check formats: are images served as WebP/AVIF, or only PNG/JPEG?24- Check for oversized images (> 500KB, > 2000px width)25- Check for `next/image`, `@astrojs/image`, or similar optimization components26- Check for `loading="lazy"` on below-the-fold images27- Check for explicit `width` and `height` attributes (prevents layout shift)2829### Step 3: Loading performance3031- Check for render-blocking resources (CSS in `<head>` without `media`, sync `<script>` without `defer`/`async`)32- Check for font loading strategy (`font-display: swap`, preloading, self-hosted vs Google Fonts)33- Check for preconnect/prefetch hints for critical third-party origins34- Check for service worker or caching configuration3536### Step 4: API performance (if applicable)3738- Check for N+1 query patterns (multiple sequential fetches that could be batched)39- Check for pagination on list endpoints40- Check for caching headers (Cache-Control, ETag)41- Check for compression middleware (gzip/brotli)4243### Step 5: Output4445```46PERFORMANCE AUDIT — <project> @ <sha>4748| # | Area | Status | Finding |49|----|------|--------|---------|50| P1 | Bundle | WARN | Total JS 420KB gzipped (target: < 300KB). Largest: vendor.js 180KB |51| P2 | Code splitting | PASS | 12 dynamic imports, route-based splitting configured |52| P3 | Images | FAIL | 8 images > 500KB, none in WebP format |53| P4 | Lazy loading | WARN | 4 below-fold images without loading="lazy" |54| P5 | Fonts | PASS | Self-hosted, font-display: swap |55| P6 | Caching | PASS | Cache-Control headers on static assets |5657Performance readiness: NEEDS WORK (1 fail, 2 warnings)58Estimated LCP improvement: -1.2s if images are optimized and lazy-loaded59```6061## Persist the run + render trend6263After printing the findings table, persist via the shared audit-history lib so the performance trend across runs becomes legible. See `docs/agdr/AgDR-0019-audit-artefact-persistence.md`.6465### Resolve project name + score + verdict6667`<project-name>` from `apexyard.projects.yaml` (or basename + `/handover` reminder if unregistered).6869Score: `score = max(0, 100 - 25*critical - 10*high - 3*medium - 1*low)`. Verdict by worst-severity: critical/high → `fail`, medium → `conditional`, low/none → `pass`. Legacy "Performance readiness" three-state: NEEDS WORK → `fail`/`conditional` (judgement call by counts), GOOD → `pass`.7071### Persist + render7273```bash74source "$(git rev-parse --show-toplevel)/.claude/hooks/_lib-audit-history.sh"7576# Lowercase severity in the payload — the lib expects critical/high/medium/low/info.77payload=$(mktemp); cat > "$payload" <<'EOF'78{79 "schema_version": 1,80 "findings": [81 {"id": "P1", "severity": "high", "status": "open", "summary": "JS bundle 380KB gzipped (budget 250KB)"},82 {"id": "P3", "severity": "medium", "status": "open", "summary": "loading=lazy missing on 14 below-fold images"},83 {"id": "P5", "severity": "high", "status": "open", "summary": "Cache-Control no-cache on static assets — misses CDN edge caching"}84 ]85}86EOF8788# Body: per templates/audits/performance-audit.md89body=$(mktemp); cat > "$body" <<'EOF'90... (filled-in body — findings table + Recommended priority + estimated impact) ...91EOF9293ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")94audit_run_persist "<project-name>" "performance-audit" "$ts" "fail" 60 "$body" < "$payload"95rm -f "$payload" "$body"9697audit_render_trend "<project-name>" "performance-audit" 598```99100### Opt-in commit101102```bash103touch projects/<name>/audits/performance-audit/.audit-history-tracked104```105106## Rules1071081. **Auto-PASS for non-web projects.** APIs, CLIs, libraries measure performance differently.1092. **Measure what you can from code.** This skill reads source files and build output — it doesn't run Lighthouse. Suggest running Lighthouse separately for runtime metrics.1103. **Give specific file names and sizes** for the largest offenders, not just "bundle is too big."1114. **Estimate impact** where possible ("optimizing these 3 images would save ~800KB and improve LCP by ~1s").1125. **Always persist via the lib.** The persist step runs regardless of opt-in commit state.1136. **Severity vocabulary in the JSON is lowercase.** The lib expects `critical`/`high`/`medium`/`low`/`info`.