name: perf-audit
description: Performance audit: bundle size, lazy loading, data fetching, caching, N+1 queries, image optimization. Native mode checks memory, I/O, launch weight, energy.
user-invocable: true
model: sonnet
context: fork
allowed-tools: Read Glob Grep Bash Agent Edit Write
argument-hint: [target:section:|target:page:|mode:audit|mode:apply]
Configuration (adapt before first run)
Replace these placeholders:
[FRAMEWORK] - e.g. Next.js App Router, Remix, SvelteKit, plain React
[SITEMAP_OR_ROUTE_LIST] - e.g. docs/sitemap.md
[API_ROUTES_PATH] - e.g. routes/, src/handlers/, api/
[BUNDLE_TOOL] - e.g. @next/bundle-analyzer, vite-bundle-visualizer, webpack-bundle-analyzer
[PERF_TOOL] - profiling tool for native/backend stacks - e.g. Instruments (Swift), Android Profiler (Kotlin), perf / flamegraph (Rust/Go), cProfile (Python), dotnet-trace (.NET). If not configured, the skill runs static analysis only and recommends profiling setup.
[PROFILER_COMMAND] - command to generate a profile - e.g. xcrun xctrace record --template 'Time Profiler', go tool pprof, cargo flamegraph, python -m cProfile -o profile.out. If not configured, the skill skips the profiling step and proceeds with static analysis.
- Note: if this is a public-facing app, remove the "Out of scope" restriction on Core Web Vitals
Applicability check
Before any other step: read CLAUDE.md and check the Framework and Language fields.
- If the project is a web application (Framework is NOT
N/A - native app and NOT N/A - no web frontend): proceed to Step 1 - Web Performance Audit (Steps 1–5).
- If the project is a native or backend-only application (Framework is
N/A - native app or N/A - no web frontend): skip Steps 1–5 and proceed directly to Step 6 - Native/Backend Performance Audit.
Context and scope
In scope: bundle composition, server/client component boundaries, dynamic rendering triggers, lazy loading, data fetching efficiency (caching, parallelism, N+1), re-render patterns, image optimization. For native stacks: algorithmic complexity, stack-specific hot-path patterns, memory management, launch weight, energy impact, binary size.
Out of scope: SEO, CWV as ranking signals, robots.txt, sitemap.xml, OG tags, Lighthouse site audit, accessibility (→ /ui-audit), DB schema (→ /skill-db).
Default mode: audit (report only, no code changes). mode:apply makes focused non-breaking fixes.
CWV reference (informational only - not primary deliverable for this internal app):
| Metric | Good | Needs work | Poor | Primary cause |
|---|
| LCP | ≤ 2.5s | 2.5–4s | > 4s | Slow server response, render-blocking resources |
| CLS | ≤ 0.1 | 0.1–0.25 | > 0.25 | Unsized images, dynamic content above fold |
| INP | ≤ 200ms | 200–500ms | > 500ms | Blocking JS on event handlers |
Source: web.dev/articles/vitals. Use these thresholds only as triage anchors - not as primary deliverables.
Step 0 - Target resolution
Parse $ARGUMENTS for target: and mode: tokens.
Operating modes:
| Mode | Behavior |
|---|
mode:audit (default) | Report only - no code changes |
mode:apply | Apply focused, non-breaking fixes (lazy loading wrappers, Promise.all parallelization). Describe each change before applying. |
Target resolution:
| Pattern | Meaning |
|---|
target:page:/dashboard | Focus on the dashboard and its component tree (example) |
| No argument | Full audit - ALL page files + layout files + lib files + API routes from sitemap.md. Maximum depth. |
STRICT PARSING - mandatory: derive target ONLY from the explicit text in $ARGUMENTS. Do NOT infer target from conversation context, recent work, active block names, or project memory. If $ARGUMENTS contains no target: token → full audit across ALL files from sitemap.md at maximum depth. When a target IS provided → act with maximum depth and completeness on that specific scope only.
Target scoping - comparative vs independent checks (mandatory): a subset of checks evaluate a project-wide property (build configuration, client bundle composition, launch entry point, release artifact); the rest evaluate one file or route in isolation.
- Comparative checks - full-project by design; the
target: filter does NOT apply to them: P1, P2, P3 (web) and NR1, NR4 (native). They MUST run against the full project even when a target is given. The client bundle, build configuration, entry point, and release artifact are global - narrowing them to the target subset hides cross-cutting exposure (a heavy package bundled by a non-targeted page still ships to every user).
- Independent checks - target-safe: every other check (B1-B9, Q1-Q3, NP1-NP4, Step 6b, NR2, NR3) evaluates a single file/route and safely honors the
target: filter. Layout/shell files stay in scope regardless of target (Step 1).
- When a
target: is given: apply the filter to the independent checks only; run the comparative checks across the full project and state in the report that they ran full-project by design.
Announce: Running perf-audit - scope: [FULL | target: <resolved>] - mode: [audit | apply]
Apply the target filter to the file list in Step 1.
Step 1 - Build file inventory
- All page/view files in scope
- All heavy components (those with chart, calendar, data table, rich text editor - high rendering cost)
- All layout/shell files (always include regardless of target - critical for B1 Flag B and B8)
- All service/data-access files directly called from page/component files - exclude pure utility files with no DB calls
- Framework configuration file (e.g.
next.config.ts, vite.config.ts, nuxt.config.ts, webpack.config.js)
Read docs/refactoring-backlog.md - note existing PERF- entries to avoid duplicates.
Step 2 - Server/Client boundary checks (Explore agent)
Launch a single Explore subagent (model: haiku) with all page, component, and lib files from Step 1:
"Read ${CLAUDE_SKILL_DIR}/PATTERNS.md for framework-specific grep patterns. Run all 9 checks below. For each: state total match count, list every match as file:line - excerpt, and state PASS or FAIL.
CHECK B1 - Unnecessary client-side rendering scope
Find components or pages marked for client-side rendering that do not actually use browser APIs (event handlers, state, refs, browser-only hooks). See PATTERNS.md → B1 for client-side directives per framework.
- Flag A: any client-marked file that uses NONE of: state management, event handlers, refs, or browser APIs. It may be renderable on the server.
- Flag B: any client-marked layout or provider file that imports 5+ child components - if only a small interactive portion requires client rendering, extracting it into an island would let the layout render on the server.
CHECK B2 - Heavy libraries in client bundle
Grep for imports of known-heavy libraries in client-rendered files. Flag any imports of libraries that do NOT need browser APIs and could run server-side:
- Document processing libraries (PDF, XLSX, etc.) - typically server-only
- Rich text editors - acceptable in client IF interactive; flag if read-only render
- Chart libraries - acceptable in client if interactive; flag if static data-display only
- Any library > ~50KB that has a server-side equivalent
Flag: each import with the library name and whether a server-side pattern exists.
CHECK B3 - Client-side data fetching that defeats server rendering
Find client-rendered components that fetch data in lifecycle hooks (see PATTERNS.md → B3) instead of using server-side data loading.
Flag: data fetching in client lifecycle hooks - these defeat server rendering and add a loading waterfall. Use server-side data loading with streaming/suspense, or a client-side cache library with proper revalidation.
CHECK B4 - Unstable callback references defeating memoization
Find inline arrow functions passed as props to memoized child components. Inline functions create new references on every parent render, defeating memoization.
Flag: each case. Skip native HTML element event handlers - frameworks batch these internally.
CHECK B5 - Sequential await waterfall
Pattern: two or more consecutive await calls for independent data sources in the same async function.
Grep in server-rendered files or API routes: lines matching const .* = await that appear consecutively (within 5 lines of each other) without the first result being an input to the second call.
Flag: each pair of sequential awaits that could be parallelised with Promise.all (or equivalent).
Example of violation: const a = await getA(); const b = await getB(); where b doesn't depend on a.
CHECK B6 - Missing caching on repeated server-side queries
Find data fetches that run on every request without caching in server-rendered components.
Grep in server-rendered files for database query calls - check if they use any caching mechanism (framework cache, memoization, or explicit cache headers).
Flag: uncached queries in layout-level or frequently-visited components (e.g. navigation data, user profile). These execute on every page visit.
CHECK B7 - Images without explicit dimensions (CLS risk)
Find image elements (framework image component or native <img>) without width/height or fill/cover constraints.
Flag: each unsized image. Without dimensions, the browser cannot reserve layout space - content shifts when the image loads, causing CLS violations.
CHECK B8 - Accidental dynamic rendering triggers
Find patterns that force dynamic rendering at the layout level, opting entire route subtrees out of static generation:
- Request-scoped APIs (cookies, headers, session) called in layout files
- Explicit force-dynamic configuration in page/layout files
- No-cache directives in layout-level data fetches
Flag: each occurrence. Verify from context whether intentional (auth-dependent, real-time data) or avoidable with proper caching or component restructuring.
CHECK B9 - Missing lazy loading for heavy client components
Find imports of heavy libraries (rich text editors, chart libraries, complex UI components) that are statically imported without lazy loading.
Flag: any file where a heavy component is eagerly imported without using the framework's dynamic/lazy import mechanism (see PATTERNS.md → B9)."
Step 3 - Bundle composition check (main context)
Read the framework configuration file.
P1 - Bundle analyzer availability
Check if a bundle analyzer is configured or available for the project's build tool (see PATTERNS.md → P1 for tool names per build system).
If no analyzer is configured or documented, flag as Medium - developers cannot easily audit bundle composition.
P2 - Server-only package exclusion
Check if the framework configuration excludes heavy server-only packages from the client bundle.
Identify packages in the project that should never be client-bundled (e.g. PDF processors, XLSX generators, database drivers, file system utilities).
Flag: any heavy server-only package not explicitly excluded from client bundling.
P3 - Tree-shaking optimization for large libraries
Check if the build configuration optimizes imports for large libraries with many exports where only a subset is used (e.g. icon libraries with hundreds of icons, utility libraries like lodash).
Note: some frameworks automatically optimize certain packages (check the framework docs). If a library is already auto-optimized by the build tool, note as PASS.
Flag: any package with 100+ exports where only a subset is used, not covered by the build tool's tree-shaking or import optimization.
Step 4 - API query efficiency (Explore agent - separate pass)
"Run these 3 checks. Adapt grep patterns to the project's database client or ORM:
CHECK Q1 - Unbounded queries (no limit on large-growth tables)
Flag: each collection query without pagination bounds. Exception: export routes that intentionally fetch all for CSV/XLSX - verify from context.
CHECK Q2 - Select * (over-fetching columns)
Grep for select-all patterns in route handlers (see PATTERNS.md → Q2 for ORM-specific patterns).
Flag: each match. Fetching all columns is a performance and security risk - columns with large values are sent over the wire unnecessarily.
CHECK Q3 - N+1 patterns
Pattern A: database query calls inside .map(, for, or forEach loops.
Pattern B: sequential await with a DB client call inside a loop body.
Flag: each match. N+1 on a list endpoint means N DB queries for an N-item list - use batch queries or embedded/eager loading instead."
Step 5 - Produce report and update backlog
Generate the report using the template in ${CLAUDE_SKILL_DIR}/REPORT.md (Web Audit section). Apply the severity guide and backlog writing rules from the same file.
Execution notes
- In
mode:audit (default): do NOT make any code changes - report only. After producing the report, ask: "Should I implement the High/Critical priority optimisations?"
- In
mode:apply: apply only the fixes listed in Quick wins (isolated, non-breaking). Describe each change before writing it. Do not apply Strategic refactors without explicit user confirmation. Do NOT ask the closing question - the user already expressed intent via mode:apply.
- Check CLAUDE.md "Known Patterns" for intentionally configured server-external packages, client-side component exclusions, or layout-level client directives before flagging them.
Step 6 - Native/Backend Performance Audit
This path runs for non-web projects only. Web projects use Steps 1–5 above.
Profiling tools
Primary tool: [PERF_TOOL]
Profile command: [PROFILER_COMMAND]
Run the profiler on the main execution path. Identify the top 5 hotspots by CPU time and the top 5 by memory allocation. If profiling data is not available, proceed with static analysis and recommend running the profiler.
Step 6a - Algorithmic and resource checks (Explore agent)
Launch a single Explore subagent (model: haiku) with all source files from the project:
"Run these 4 checks on the provided source files. For each: state total match count, list every match as file:line - excerpt, and state PASS or FAIL.
CHECK NP1 - Nested iteration on collections
Grep for nested loops (for/while/map/forEach inside another loop body) operating on collections or arrays.
Flag: O(n²) or worse complexity. Exclude small fixed-size iterations (< 10 items known at compile time).
CHECK NP2 - Memory allocation in hot paths
Flag:
- Object/string/array/buffer creation inside loops (new allocation per iteration)
- Unbounded caches or collections that grow without eviction
- Missing cleanup of heavyweight resources (file handles, DB connections, network sockets)
CHECK NP3 - I/O bottleneck patterns
Flag:
- Synchronous file I/O on the main/UI thread
- Sequential network calls that could be parallelized
- Unbuffered reads/writes on large files
- Missing timeout on network or IPC operations
CHECK NP4 - Concurrency inefficiency
Flag:
- Thread/goroutine/task creation inside loops without pooling
- Shared mutable state without synchronization primitives
- Blocking calls on async/concurrent paths
- Missing cancellation support on long-running operations"
Step 6b - Stack-specific checks (main context)
Read the 10 largest source files by line count. Apply the checks relevant to the project language using grep patterns from PATTERNS.md → Step 6b (organized by language). Run the patterns, then read flagged files for context.
Step 6c - Resource footprint checks (main context)
CHECK NR1 - Launch / startup weight
Identify the application entry point (see PATTERNS.md → NR1 for per-language markers). Grep for heavy operations in the entry point: database initialization, network calls, large file reads, complex object graph construction. Flag any operation that could be deferred (lazy initialization) or moved to a background thread/task.
CHECK NR2 - Memory management patterns
Grep for per-language memory antipatterns from PATTERNS.md → NR2. Key categories: missing autorelease pools in batch loops, unbounded caches, leaked references, pre-allocation misses. Also flag large static/global collections that persist for the process lifetime.
CHECK NR3 - Energy and background patterns (mobile stacks only)
Grep for per-language energy antipatterns from PATTERNS.md → NR3. Key categories: tight timers without tolerance, high-frequency location updates, excessive wake-ups. Flag any background polling pattern that could use push notifications or event-driven updates instead.
CHECK NR4 - Binary / artifact size
- Grep for large embedded assets in source directories (images > 1MB, bundled databases, embedded fonts). Flag if assets could be downloaded on demand.
- Check for debug-only code or dependencies leaking into release builds.
- Grep for unused imports at module/package level - flag files importing modules they don't use.
Step 6d - Produce report and update backlog
Generate the report using the template in ${CLAUDE_SKILL_DIR}/REPORT.md (Native Audit section). Apply the severity guide and backlog writing rules from the same file.
Native audit - execution notes
- In
mode:audit (default): report only. After report, ask: "Should I implement the High/Critical priority optimisations?"
- In
mode:apply: apply only Quick wins. Describe each change before writing.