# Performance Audit

> Performance audit — bundle size, image opt, lazy load, code split, caching, CWV. Deep-dive for /launch-check performance.

- Skill: `me2resh/performance-audit` (Agent Skill)
- Install (CLI): `npx skillmds@latest add me2resh/performance-audit`
- Raw SKILL.md: https://api.skillmd.com/api/skills/me2resh/performance-audit/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: me2resh (https://skillmd.com/u/me2resh)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/me2resh/performance-audit

---


## Writing rule

When this skill writes a durable artifact, read .claude/rules/writing-standard.md. Use the controlled technical writing profile.

# /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

```bash
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

```bash
touch projects/<name>/audits/performance-audit/.audit-history-tracked
```

## Rules

1. **Auto-PASS for non-web projects.** APIs, CLIs, libraries measure performance differently.
2. **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.
3. **Give specific file names and sizes** for the largest offenders, not just "bundle is too big."
4. **Estimate impact** where possible ("optimizing these 3 images would save ~800KB and improve LCP by ~1s").
5. **Always persist via the lib.** The persist step runs regardless of opt-in commit state.
6. **Severity vocabulary in the JSON is lowercase.** The lib expects `critical`/`high`/`medium`/`low`/`info`.

---

*Part of [ApexYard](https://github.com/me2resh/apexyard) — multi-project SDLC framework for Claude Code · MIT.*

