# Performance Audit

> Audit runtime performance patterns in a TypeScript/React frontend (render, network/data, assets/CWV, main-thread). Static-first with optional --with-lighthouse-results enrichment and implementation plan.

- **Type:** Skill
- **Install:** `agentstack add skill-bensheridanedwards-architectplaybook-performance-audit`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [BenSheridanEdwards](https://agentstack.voostack.com/s/bensheridanedwards)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [BenSheridanEdwards](https://github.com/BenSheridanEdwards)
- **Source:** https://github.com/BenSheridanEdwards/ArchitectPlaybook/tree/main/performance-audit

## Install

```sh
agentstack add skill-bensheridanedwards-architectplaybook-performance-audit
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# /performance-audit

Audit a TypeScript and React frontend's **runtime** performance patterns against an opinionated baseline organised in four layers — **render performance**, **network and data**, **assets, media, and Core Web Vitals**, **main-thread work and measurement** — preceded by a diagnostic snapshot. Then offer to generate an implementation plan for the gaps.

## How this differs from neighbouring audits

The architect-playbook deliberately keeps audit boundaries tight so a single concern lives in a single skill. The split:

| Concern | Owner |
| --- | --- |
| Bundle size, build time, code splitting at the build-tool level | `/bundle-build-audit` |
| God components, file size, fan-out as architectural invariants | `/architecture-audit` |
| Hook correctness, idiomatic React patterns, ecosystem library usage | `/react-audit` |
| **Runtime cost of rendering, including React-specific levers** (memoization, list virtualization, context churn) | `/performance-audit` |
| **Network and data-fetching patterns at runtime** (waterfalls, stale times, prefetch) | `/performance-audit` |
| **Asset usage at runtime** (LCP priority, lazy loading, layout shift) | `/performance-audit` |
| Asset *optimisation* configuration (formats, hashed filenames, image primitive setup) | `/bundle-build-audit` |
| Static analysis configuration (which performance lint rules are enabled) | `/linting-audit` |

When a single gap is relevant to two audits (for example, the Lighthouse CI configuration check is shared between this skill and `/bundle-build-audit`), both surface it. A single fix passes both.

## Static-first design with optional Lighthouse-results enrichment

This skill is read-only and never runs the application. Two modes:

- **Static (default).** Pattern detection across source files, framework configuration, and the configuration of any detected performance-monitoring tools.
- **Static plus opt-in `--with-lighthouse-results`.** When the flag is passed and a Lighthouse JSON results file exists at the default path (`lighthouse-results.json`) or at `--lighthouse-results-path=`, the skill reads the captured Largest Contentful Paint, Interaction to Next Paint, Cumulative Layout Shift, Total Blocking Time, First Contentful Paint, and overall performance score. They feed the diagnostic snapshot and a small number of run-required checks (LCP-image cross-validation, performance-score budget verification).

The skill **never runs Lighthouse itself**. Spinning up a server and running an end-to-end performance test has significant side effects (build, serve, network I/O); that is the user's job, the responsibility of a separate fix-and-validate skill, or a continuous-integration step. Keeping this audit fully read-only is what makes it safe to run in any working tree at any time.

## Usage

```
/performance-audit                                # default: concise Top 5 + full report saved + ask about plan
/performance-audit --worktree                          # create an isolated Git worktree, then run the audit there
/performance-audit --learn                        # mid-level engineer teaching mode (detailed explanations + file/line examples)
/performance-audit --teach                        # alias for --learn
/performance-audit --with-lighthouse-results      # static plus enrichment from existing Lighthouse JSON
/performance-audit --lighthouse-results-path=path/to/lighthouse.json  # override default lighthouse-results.json
/performance-audit --threshold-virtualization=100 # override default 50 items
```

**💡 Pro tip**: Add `--worktree` to run this audit in an isolated Git worktree.

The skill never accepts `--apply`. The implementation plan is descriptive Markdown.

The defaults baked into the skill are the recommended baseline. The list-virtualization threshold is tunable via flag; all other checks are zero-tolerance or qualitative. The canonical path to evolving the baseline itself is `/system-self-improve`.

**💡 Pro tip**: Run `/preflight --audit=performance` first to detect — and optionally install — the development dependency that makes `--with-lighthouse-results` useful (`lighthouse` as a development dependency), and to scaffold a minimal `.lighthouserc.json` if one is missing. Skip if you already know the tooling is wired up.

## The opinionated baseline

A check resolves to one of four statuses:

- **present** — the invariant holds.
- **partial** — most signals resolve, with a small number of exceptions, or the codebase shows mixed adherence to a soft check.
- **missing** — a structural prerequisite is absent (no performance-monitoring provider installed, for example).
- **violation** — the audit identified concrete code that breaks the invariant.

Layer 0 is informational only and has no status.

### Layer 0 — Diagnostic snapshot (always written, no pass/fail)

- Detected meta-framework: Next.js App Router or Pages Router, Remix, Vite-React, Create React App, plain React.
- Detected data-fetching layer: TanStack Query, SWR, RTK Query, Apollo Client, native fetch, custom — or none beyond ad-hoc.
- Detected performance / analytics provider: Vercel Analytics, Vercel Speed Insights, Datadog RUM, Sentry Performance, the `web-vitals` package wired to a custom endpoint — or none.
- Image primitive in use: `next/image`, `@unpic/react`, `astro:assets`, native ``, mixed.
- Font loading strategy detected: Next.js `next/font`, framework-managed, manual ``, mixed.
- Virtualization library presence: `react-window`, `react-virtual`, `@tanstack/react-virtual`, `react-virtuoso`, none.
- Service worker / Progressive Web App detected: yes/no, registration source.
- Captured Web Vitals from Lighthouse results when available: LCP, INP, CLS, TBT, FCP, plus the overall performance score.

### Layer 1 — Render performance

| Check | Expectation | Violation signal |
| --- | --- | --- |
| Expensive computations memoized | Sort, filter, derive-from-list, and other O(n) or higher derivations inside component bodies are wrapped in `useMemo` (or moved out of render). | A computation involving a known-expensive method (`.sort`, `.filter`, `.flatMap`, `.reduce`) at the top level of a render function with non-trivial inputs and no `useMemo` wrapping. |
| Stable references for memo'd children's props | Components wrapped in `React.memo` receive object/array/function props that are stable across renders (constructed via `useMemo`/`useCallback` or constants). | An inline object literal or arrow function passed to a `React.memo`-wrapped child as a prop. |
| Stable Context values | Context provider values are wrapped in `useMemo` so consumers don't re-render on every parent render. | `` with a fresh object literal each render. |
| Long lists virtualized | Lists rendering more than the threshold (default 50; tunable via `--threshold-virtualization`) of repeating items use a virtualization library. | A `.map()` rendering more items than the threshold without a virtualization wrapper. |
| Stable list keys | List keys are stable, unique, and not the array index when items can be inserted, removed, or reordered. | `key={index}` patterns in lists where the data shape suggests reordering is possible. |
| `React.memo` for pure components in hot paths | Pure components rendered many times per parent render (typically inside lists or hot interaction states) are wrapped in `React.memo`. Soft check — `partial`. Graphify-aware: when the graph is present, "frequently rendered" is computed from inbound render edges. | Frequently-rendered pure components without `React.memo` wrapping. |
| No expensive work in render without memoization | Render bodies don't synchronously perform large transformations (parsing, deep cloning, recursive walks) without `useMemo`. | Detected hot patterns running unconditionally in render bodies. |
| `useState` lazy initialiser for heavy initial values | Initial values that are themselves expensive to compute use the lazy initialiser form (`useState(() => expensiveCompute())`). | `useState(expensiveCompute())` with a non-trivial right-hand side that runs on every render. |

### Layer 2 — Network and data

| Check | Expectation | Violation signal |
| --- | --- | --- |
| Single data-fetching strategy | Exactly one of TanStack Query, SWR, RTK Query, Apollo Client, or framework-native (Next.js loader/server components, Remix loader). Not multiple in the same application. | Two or more strategies present in dependencies and used in source. |
| Stale-time configured | The detected data layer has a non-zero default `staleTime` (or equivalent). The SDK default of zero means every consumer refetches on mount. Soft check — reported as `partial` if some queries override but no global default exists. | Default `staleTime` of 0 in the global query client configuration. |
| Parallel fetches use parallel patterns | Multiple independent fetches in the same render path use `Promise.all`, parallel `useQuery` calls, or Suspense's parallel-by-default behaviour — not sequential `await` chains. | Sequentially awaited fetches that have no data dependency between them. |
| No N+1 fetch patterns | Lists don't fire one fetch per row from inside list items. | A `useQuery` (or equivalent) called inside a list item's render path. |
| Request deduplication available | The detected data layer deduplicates concurrent requests for the same key. All four major libraries provide this by default; flagged when using a custom client without it. | A custom data layer with no deduplication. |
| Prefetching for predictable navigation | Links to known navigation targets prefetch (Next.js `` default behaviour, TanStack Query's `prefetchQuery`, SWR's prefetch helpers). Soft check — reported as `partial`. | A custom ``-based navigation in apps where the data layer supports prefetching. |
| Streaming and Suspense for slow data (Next.js App Router only) | Long-loading data sections in Next.js App Router are wrapped in Suspense boundaries with meaningful fallbacks. | Slow data fetches in route handlers or server components that block the entire route render. Skipped silently when not in Next.js App Router. |
| Server-rendered data not re-fetched on client | Data already available from a server component, route loader, or initial server render is not re-fetched on the client. | Patterns where server-rendered data is also fetched via a client-side hook. |

### Layer 3 — Assets, media, and Core Web Vitals

| Check | Expectation | Violation signal |
| --- | --- | --- |
| Framework image primitive used | Images use the framework's image component (`next/image`, `@unpic/react`, equivalent). Native `` is reserved for icon-sized SVG and decorative purposes. | Plain `` tags pointing at content imagery in a framework that provides an image primitive. |
| LCP image marked priority | At least one image per landing route is marked as the priority/eager-loaded LCP candidate (`priority` prop in Next.js, `loading="eager"` plus `fetchpriority="high"` in plain HTML). When `--with-lighthouse-results` confirms the actual LCP element, the audit cross-checks; otherwise it heuristically requires a priority hint to exist somewhere on hero/landing routes. | No priority/eager-loaded image on landing routes, or the heuristically-identified hero image is not the priority one. |
| Explicit dimensions on images | Every image has explicit `width` and `height` attributes, or CSS `aspect-ratio`, so the browser can reserve space and avoid Cumulative Layout Shift. | An `` (native or framework) with no dimensions and no aspect-ratio CSS. |
| Lazy loading below-the-fold images | Below-the-fold images use lazy loading (framework default, or `loading="lazy"`). | Forced eager loading on below-the-fold imagery. |
| Fonts use `font-display: swap` (or optional) | Web fonts are configured with `font-display: swap` (or `optional` for tightly-controlled experiences), with critical fonts preloaded. | Fonts loaded without `font-display`, or with `font-display: block` (which causes invisible text periods). |
| Critical CSS handled | The framework handles critical CSS extraction, OR the project explicitly inlines critical CSS for hero/landing routes. Soft check — reported as `partial`. | Neither path is set up. |
| No layout-shifting late content | Content that loads asynchronously (advertisements, embeds, iframes, dynamic recommendations) has reserved space (height or aspect-ratio) so it doesn't push other content when it arrives. | Async-loading content with no reserved dimensions. |
| Video autoplay is muted | `` is paired with `muted`. (Browsers block unmuted autoplay anyway, but the explicit attribute prevents the playback failure path.) | Autoplay videos without `muted`. |

### Layer 4 — Main-thread work and measurement

| Check | Expectation | Violation signal |
| --- | --- | --- |
| Long computations deferred or workered | Computations that visibly block the main thread (large data parsing, heavy synchronous work) use `useDeferredValue`, `startTransition`, `requestIdleCallback`, or a Web Worker. Soft check — reported as `partial`. | Detected hot patterns running synchronously in event handlers or effects. |
| Scroll/resize/mousemove handlers throttled | High-frequency event handlers (scroll, resize, mousemove, drag) are debounced or throttled. | `addEventListener('scroll', ...)` or React equivalents firing without throttling. |
| Animations use compositor-friendly properties | Animations that change visual state use `transform`, `opacity`, `filter` — not `width`, `height`, `top`, `left`, `margin`. | CSS animations or JS-driven animations that touch layout properties. |
| Web Vitals reporting integrated | The project reports Web Vitals to a backend: the `web-vitals` package wired to an analytics endpoint, framework-provided analytics, Vercel Speed Insights, Datadog RUM, or Sentry Performance. | None detected. |
| Real User Monitoring integrated | A Real User Monitoring provider is configured for production. (Overlaps with Web Vitals reporting; the audit accepts the same providers.) | None detected. |
| Lighthouse CI configured | Lighthouse CI is configured to run in continuous integration with thresholds for performance score and Core Web Vitals. (Overlap with `/bundle-build-audit`'s budget check; both surface the gap so a single fix passes both.) | No Lighthouse CI configuration detected. |
| Performance budgets defined and enforced | Beyond bundle-size budgets (owned by `/bundle-build-audit`), the project enforces budgets on Lighthouse-derived metrics (performance score, LCP, INP, CLS) and continuous integration fails the build when they regress. | No metric budgets defined, or defined but not enforced. |

## What this skill does

1. **Reads the knowledge graph when present.** Soft dependency: when `graphify-out/graph.json` exists, the audit identifies hot render paths (god components rendered on every navigation, central data hooks consumed by many components) and uses centrality to prioritise the implementation plan. Without the graph, the audit operates on per-file pattern detection with reduced precision.
2. **Confirms a TypeScript and React project.** Detects `package.json`, `tsconfig.json`, and `react` in dependencies. If any are absent, the skill stops and tells the user it currently supports TypeScript and React frontend projects only.
3. **Detects framework, data layer, performance provider, and image primitive** for the diagnostic snapshot and for resolving framework-conditional checks.
4. **When `--with-lighthouse-results` is set**, reads the JSON file at the resolved path and extracts LCP, INP, CLS, TBT, FCP, and the overall performance score. If the file is missing or unparseable, prints to the chat and prepends to `findings.md`: "`--with-lighthouse-results` was requested but no usable results file was found. Run `/preflight --audit=performance --install --scaffold-configs` to install Lighthouse and scaffold a minimal `.lighthouserc.json`, then run Lighthouse to produce the results file and re-run

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [BenSheridanEdwards](https://github.com/BenSheridanEdwards)
- **Source:** [BenSheridanEdwards/ArchitectPlaybook](https://github.com/BenSheridanEdwards/ArchitectPlaybook)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-bensheridanedwards-architectplaybook-performance-audit
- Seller: https://agentstack.voostack.com/s/bensheridanedwards
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
