Install
$ agentstack add skill-bensheridanedwards-architectplaybook-performance-audit ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →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-vitalspackage 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
- Reads the knowledge graph when present. Soft dependency: when
graphify-out/graph.jsonexists, 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. - Confirms a TypeScript and React project. Detects
package.json,tsconfig.json, andreactin dependencies. If any are absent, the skill stops and tells the user it currently supports TypeScript and React frontend projects only. - Detects framework, data layer, performance provider, and image primitive for the diagnostic snapshot and for resolving framework-conditional checks.
- When
--with-lighthouse-resultsis 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 tofindings.md: "--with-lighthouse-resultswas requested but no usable results file was found. Run/preflight --audit=performance --install --scaffold-configsto 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
- Source: BenSheridanEdwards/ArchitectPlaybook
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.