Audit an Angular application's runtime performance, bundle size, change detection strategy, and optimization patterns. Focuses on fast initial load and smooth runtime.
Analyzes Angular app performance issues affecting load time and runtime smoothness. Use this when optimizing bundle size, change detection, or identifying memory leaks before production.
/plugin marketplace add aaronmaturen/claude-plugin/plugin install atm@aaronmaturen-pluginsAudit an Angular application's runtime performance, bundle size, change detection strategy, and optimization patterns. Focuses on fast initial load and smooth runtime.
Related audits:
/angular-style-audit - Material Design, theming, CSS patterns/angular-architecture-audit - Services, DI, state management, component structureUsage:
/angular-performance-audit - Full audit of current directory/angular-performance-audit /path/to/app - Full audit of specified path/angular-performance-audit --branch or -b - Audit only files changed in current branch/angular-performance-audit --branch /path/to/app - Branch audit in specified pathThis audit focuses on perceived and actual performance. The goal is identifying:
# Parse arguments for branch mode
BRANCH_MODE=false
APP_PATH="."
for arg in $ARGUMENTS; do
case "$arg" in
--branch|-b)
BRANCH_MODE=true
;;
*)
APP_PATH="$arg"
;;
esac
done
# Verify Angular app
if [[ ! -f "$APP_PATH/angular.json" ]] && [[ ! -f "$APP_PATH/package.json" ]]; then
echo "❌ No Angular app found at: $APP_PATH"
exit 1
fi
# Branch mode setup
if [[ "$BRANCH_MODE" == true ]]; then
CURRENT_BRANCH=$(git branch --show-current)
BASE_BRANCH="main"
# Get changed files (performance: .ts, .html, .scss for component analysis)
CHANGED_FILES=$(git diff --name-only "$BASE_BRANCH"...HEAD 2>/dev/null | grep -E '\.(ts|html|scss)$' | grep -v "\.spec\.ts$" | grep -v "node_modules")
if [[ -z "$CHANGED_FILES" ]]; then
echo "⚠️ No relevant files changed compared to $BASE_BRANCH"
echo " (Looking for: .ts, .html, .scss)"
echo ""
echo " Run without --branch for full audit"
exit 0
fi
echo "🌿 BRANCH MODE: Performance audit of files changed in current branch"
echo " Branch: $CURRENT_BRANCH"
echo " Comparing to: $BASE_BRANCH"
echo " Files to audit: $(echo "$CHANGED_FILES" | wc -l | tr -d ' ')"
echo ""
# Categorize changed files
COMPONENT_FILES=$(echo "$CHANGED_FILES" | grep -E "\.component\.ts$")
TEMPLATE_FILES=$(echo "$CHANGED_FILES" | grep -E "\.html$")
[[ -n "$COMPONENT_FILES" ]] && echo " 🧩 Component files: $(echo "$COMPONENT_FILES" | wc -l | tr -d ' ')"
[[ -n "$TEMPLATE_FILES" ]] && echo " 📄 Template files: $(echo "$TEMPLATE_FILES" | wc -l | tr -d ' ')"
echo ""
# Helper function for branch-aware searching
search_files() {
local pattern="$1"
local file_filter="${2:-}"
if [[ -n "$file_filter" ]]; then
echo "$CHANGED_FILES" | grep -E "$file_filter" | xargs grep -n "$pattern" 2>/dev/null
else
echo "$CHANGED_FILES" | xargs grep -n "$pattern" 2>/dev/null
fi
}
count_matches() {
search_files "$1" "$2" | wc -l | tr -d ' '
}
else
echo "📂 Full audit mode: $APP_PATH"
# Full mode search helper
search_files() {
local pattern="$1"
local file_filter="${2:-*.ts}"
grep -rn "$pattern" --include="$file_filter" "$APP_PATH/src/app" 2>/dev/null
}
count_matches() {
search_files "$1" "$2" | wc -l | tr -d ' '
}
fi
# Get versions
ANGULAR_VERSION=$(grep '"@angular/core"' "$APP_PATH/package.json" | sed 's/.*: *"\([^"]*\)".*/\1/')
echo "📦 Angular: $ANGULAR_VERSION"
# Check for performance-related packages
grep -q "compression" "$APP_PATH/package.json" && echo " ✓ Compression available"
grep -q "@angular/service-worker" "$APP_PATH/package.json" && echo " ✓ Service Worker available"
grep -q "ngx-virtual-scroll\|cdk/scrolling" "$APP_PATH/package.json" && echo " ✓ Virtual scrolling available"
Note on Branch Mode: When using --branch, use search_files "pattern" "file_filter" to search only changed files. For performance audits, branch mode helps focus on new code that might introduce regressions.
# Check angular.json for production optimizations
grep -A 30 '"production"' "$APP_PATH/angular.json"
Verify these are set for production:
| Setting | Expected | Impact |
|---|---|---|
optimization | true (default) | Tree shaking, minification |
outputHashing | "all" | Cache busting |
sourceMap | false | Smaller bundles |
budgets | Configured | Prevent bloat |
namedChunks | false | Smaller chunk names |
# Extract current budgets
grep -A 10 "budgets" "$APP_PATH/angular.json"
Recommended budgets:
| Type | Warning | Error |
|---|---|---|
| initial | 500KB | 1MB |
| anyComponentStyle | 6KB | 10KB |
Flag if budgets are:
Heavy imports to flag:
# Lodash (should use lodash-es or individual imports)
grep -rn "from 'lodash'" --include="*.ts" "$APP_PATH/src"
grep -rn "import \* as _ from" --include="*.ts" "$APP_PATH/src"
# Moment.js (should use date-fns, dayjs, or native)
grep -rn "from 'moment'" --include="*.ts" "$APP_PATH/src"
# RxJS full import (should import operators individually)
grep -rn "from 'rxjs'" --include="*.ts" "$APP_PATH/src" | grep -v "from 'rxjs/operators'"
# Barrel imports that might break tree shaking
grep -rn "from '\.\./\.\./.*'" --include="*.ts" "$APP_PATH/src" | head -20
# Material importing entire modules vs individual components
grep -rn "MatModule" --include="*.ts" "$APP_PATH/src"
# Check allowedCommonJsDependencies
grep -A 5 "allowedCommonJsDependencies" "$APP_PATH/angular.json"
# These cause warnings and can't be tree-shaken
# Components using OnPush (preferred)
grep -rn "changeDetection: ChangeDetectionStrategy.OnPush" --include="*.ts" "$APP_PATH/src/app"
# Total components
TOTAL_COMPONENTS=$(find "$APP_PATH/src/app" -name "*.component.ts" | wc -l)
ONPUSH_COUNT=$(grep -rln "ChangeDetectionStrategy.OnPush" --include="*.ts" "$APP_PATH/src/app" | wc -l)
echo "OnPush: $ONPUSH_COUNT / $TOTAL_COMPONENTS components"
Target: 80%+ components should use OnPush (exceptions: forms with complex state)
# Manual change detection (often a smell)
grep -rn "ChangeDetectorRef" --include="*.ts" "$APP_PATH/src/app"
grep -rn "detectChanges()" --include="*.ts" "$APP_PATH/src/app"
grep -rn "markForCheck()" --include="*.ts" "$APP_PATH/src/app"
# ApplicationRef.tick() (almost always wrong)
grep -rn "ApplicationRef" --include="*.ts" "$APP_PATH/src/app"
Red flags:
detectChanges() called in loopsmarkForCheck() on every async operation# Running outside zone (performance optimization, but risky)
grep -rn "NgZone" --include="*.ts" "$APP_PATH/src/app"
grep -rn "runOutsideAngular" --include="*.ts" "$APP_PATH/src/app"
# @for without track (bad - full re-render on change)
grep -rn "@for" --include="*.html" "$APP_PATH/src/app" | grep -v "track"
# *ngFor without trackBy (legacy, same issue)
grep -rn "\*ngFor" --include="*.html" "$APP_PATH/src/app" | grep -v "trackBy"
# Good: trackBy or track usage
grep -rn "trackBy\|track " --include="*.html" "$APP_PATH/src/app"
Impact: Missing trackBy on lists > 20 items causes noticeable jank
# Function calls in templates (re-evaluated every CD cycle)
grep -rn "{{.*()}}" --include="*.html" "$APP_PATH/src/app"
# Method bindings in templates
grep -rn "\[.*\]=\"[a-zA-Z]*()\"" --include="*.html" "$APP_PATH/src/app"
# Complex expressions (should be computed/memoized)
grep -rn "{{\s*[^}]*\s*\?\s*[^}]*\s*:\s*[^}]*}}" --include="*.html" "$APP_PATH/src/app"
Fix: Move to computed signals or memoized getters
# Multiple async pipes on same observable (creates multiple subscriptions)
grep -rn "| async.*| async" --include="*.html" "$APP_PATH/src/app"
# Good: single async with @if/@let
grep -rn "@if.*; as \|@let" --include="*.html" "$APP_PATH/src/app"
# Lazy loaded routes (good)
grep -rn "loadComponent\|loadChildren" --include="*.ts" "$APP_PATH/src/app"
# Eagerly loaded routes (check if should be lazy)
grep -rn "component:" --include="*.ts" "$APP_PATH/src/app" | grep -v ".spec.ts"
# Check for preloading configuration
grep -rn "PreloadAllModules\|preloadingStrategy" --include="*.ts" "$APP_PATH/src/app"
| Strategy | Use Case |
|---|---|
| No preloading | Minimal initial load, pay on navigation |
| PreloadAllModules | Fast subsequent navigation, larger initial |
| Custom strategy | Preload likely routes based on analytics |
# @defer usage (progressive rendering)
grep -rn "@defer" --include="*.html" "$APP_PATH/src/app"
# Check for defer triggers
grep -rn "@defer.*on\|@placeholder\|@loading" --include="*.html" "$APP_PATH/src/app"
# CDK virtual scroll usage
grep -rn "cdk-virtual-scroll\|CdkVirtualScrollViewport" --include="*.ts" --include="*.html" "$APP_PATH/src/app"
# Large lists without virtualization (potential issue)
grep -rn "@for.*let.*of" --include="*.html" "$APP_PATH/src/app"
Rule of thumb: Lists > 50 items should consider virtualization
# Infinite scroll implementations
grep -rn "IntersectionObserver\|infinite.*scroll" --include="*.ts" "$APP_PATH/src/app"
# addEventListener without removeEventListener
grep -rn "addEventListener" --include="*.ts" "$APP_PATH/src/app" | grep -v ".spec.ts"
grep -rn "removeEventListener" --include="*.ts" "$APP_PATH/src/app" | grep -v ".spec.ts"
# fromEvent without cleanup
grep -rn "fromEvent" --include="*.ts" "$APP_PATH/src/app"
# ElementRef usage (can hold DOM references)
grep -rn "ElementRef" --include="*.ts" "$APP_PATH/src/app"
# Renderer2 (safer but still needs care)
grep -rn "Renderer2" --include="*.ts" "$APP_PATH/src/app"
# Direct document/window access
grep -rn "document\.\|window\." --include="*.ts" "$APP_PATH/src/app" | grep -v ".spec.ts"
# Chart libraries (often need manual destroy)
grep -rn "Chart\|chart\.js\|ngx-charts\|d3\." --include="*.ts" "$APP_PATH/src/app"
# Map libraries
grep -rn "google\.maps\|mapbox\|leaflet" --include="*.ts" "$APP_PATH/src/app"
# Using NgOptimizedImage (Angular 15+)
grep -rn "NgOptimizedImage\|ngSrc" --include="*.ts" --include="*.html" "$APP_PATH/src/app"
# Regular img tags (should migrate)
grep -rn "<img" --include="*.html" "$APP_PATH/src/app" | grep -v "ngSrc"
# Lazy loading attribute
grep -rn 'loading="lazy"' --include="*.html" "$APP_PATH/src/app"
# Priority hints for LCP images
grep -rn 'fetchpriority\|priority' --include="*.html" "$APP_PATH/src/app"
# Angular animations
grep -rn "@angular/animations" --include="*.ts" "$APP_PATH/src/app"
# CSS transitions (often better performance)
grep -rn "transition:" --include="*.scss" --include="*.css" "$APP_PATH/src"
# Transform-based animations (GPU accelerated - good)
grep -rn "transform:" --include="*.scss" --include="*.css" "$APP_PATH/src"
# Layout-triggering animations (bad - avoid animating width/height/top/left)
grep -rn "animate.*width\|animate.*height\|animate.*top\|animate.*left" --include="*.ts" "$APP_PATH/src/app"
# Check for SSR
grep -q "@angular/platform-server\|@angular/ssr" "$APP_PATH/package.json" && echo "SSR enabled"
# Hydration
grep -rn "provideClientHydration" --include="*.ts" "$APP_PATH/src"
# isPlatformBrowser checks (SSR compatibility)
grep -rn "isPlatformBrowser\|isPlatformServer\|PLATFORM_ID" --include="*.ts" "$APP_PATH/src/app"
REPORT_BASE="${REPORT_BASE:-$HOME/Documents/technical-analysis}"
AUDIT_DIR="${REPORT_BASE}/audits/angular-perf-$(basename $APP_PATH)-$(date +%Y%m%d)"
mkdir -p "$AUDIT_DIR"
# Angular Performance Audit Report
**Application:** [App Name]
**Date:** [Audit Date]
**Angular Version:** [Version]
## Executive Summary
### Performance Score: [A-F]
| Category | Score | Impact |
|----------|-------|--------|
| Bundle Size | | Initial load |
| Change Detection | | Runtime FPS |
| Template Efficiency | | Runtime FPS |
| Lazy Loading | | Initial load |
| Memory Management | | Long sessions |
### Key Metrics
| Metric | Current | Target |
|--------|---------|--------|
| Initial bundle | [X] KB | < 500 KB |
| OnPush adoption | [X]% | > 80% |
| trackBy coverage | [X]% | 100% |
| Lazy routes | [X]/[Y] | Max possible |
### Top 3 Performance Wins
1. **[Win]** - [Expected improvement] - [Effort]
2. **[Win]** - [Expected improvement] - [Effort]
3. **[Win]** - [Expected improvement] - [Effort]
## Detailed Findings
### Bundle Size
#### Current Budgets
```json
[current budget config]
| Package | Size | Recommendation |
|---|---|---|
| File | Issue | Fix |
|---|---|---|
Components to migrate:
| File | Method | Issue |
|---|---|---|
| detectChanges() | ||
| markForCheck() |
| File | List | Fix |
|---|---|---|
| File | Expression | Fix |
|---|---|---|
| Route | Type | Size | Recommendation |
|---|---|---|---|
| / | Eager | ||
| /feature | Lazy |
| File | Issue | Severity |
|---|---|---|
| Issue | Count | Fix |
|---|---|---|
| img without ngSrc | Migrate to NgOptimizedImage | |
| Missing lazy loading | Add loading="lazy" | |
| Missing priority | Add priority to LCP images |
After implementing fixes, measure:
ng build --stats-json + webpack-bundle-analyzerAudit Complete: [Date/Time]
## Severity Definitions
| Level | Definition | Examples |
|-------|------------|----------|
| **Critical** | Visible user impact now | > 3MB initial bundle, missing trackBy on 100+ item lists |
| **High** | Will cause problems at scale | Default change detection everywhere, memory leaks |
| **Medium** | Noticeable but manageable | Function calls in templates, missing lazy loading |
| **Low** | Optimization opportunities | Not using NgOptimizedImage, minor CD improvements |
## Performance Budget Recommendations
### Small App (< 10 routes)
- Initial: 300KB warning, 500KB error
- Per-route chunk: 50KB warning, 100KB error
### Medium App (10-50 routes)
- Initial: 500KB warning, 1MB error
- Per-route chunk: 100KB warning, 200KB error
### Large App (50+ routes)
- Initial: 750KB warning, 1.5MB error
- Per-route chunk: 150KB warning, 300KB error
- Consider micro-frontends if exceeding these consistently