Svelte Component Auditor

v1.0.0

Audit Svelte and SvelteKit components for performance, accessibility, reactive statement usage, store design, and SSR compatibility. Use when reviewing Svelt...

0· 29· 1 versions· 0 current· 0 all-time· Updated 5h ago· MIT-0

Install

openclaw skills install svelte-component-auditor

Svelte Component Auditor

Deep audit of Svelte and SvelteKit components. Analyzes reactive declarations, store patterns, accessibility, SSR readiness, and rendering performance. Produces prioritized findings with concrete fixes.

Use when: reviewing a Svelte/SvelteKit project, preparing for production, auditing accessibility, or establishing component quality standards.

Analysis Steps

1. Project Discovery

cat package.json 2>/dev/null | jq '{svelte: .devDependencies.svelte, sveltekit: .devDependencies["@sveltejs/kit"]}'
cat svelte.config.js 2>/dev/null || cat svelte.config.ts 2>/dev/null
find . -name "*.svelte" -not -path '*/node_modules/*' -not -path '*/.svelte-kit/*' | wc -l
find . -path "*/routes/*" -name "+*" -not -path '*/node_modules/*' 2>/dev/null | sort | head -20

Determine: Svelte version (4 vs 5 runes), SvelteKit presence, adapter type, component count, route structure.

2. Reactive Statement Audit

# Reactive declarations ($: label syntax — Svelte 4)
grep -rn '^\s*\$:' --include="*.svelte" . 2>/dev/null | head -25

# Reactive assignments that won't trigger (array mutation)
grep -rn '\$:.*\.push\|\$:.*\.splice\|\$:.*\.sort' --include="*.svelte" . 2>/dev/null | head -15

# Complex reactive chains (>5 per component = smell)
for f in $(find . -name "*.svelte" -not -path '*/node_modules/*' 2>/dev/null); do
  count=$(grep -c '^\s*\$:' "$f" 2>/dev/null)
  if [ "$count" -gt 5 ]; then echo "COMPLEX($count): $f"; fi
done | head -15

# Svelte 5 runes
grep -rn '\$state\|\$derived\|\$effect\|\$props' --include="*.svelte" --include="*.svelte.ts" . 2>/dev/null | head -15

Check for:

  • Mutating arrays in reactive declarations: $: items.push(x) won't trigger — must reassign: items = [...items, x]
  • Circular reactive dependencies: cause runtime errors
  • Over-complex reactive chains: >5 $: declarations signals need for store or utility extraction
  • $effect without cleanup: effects with subscriptions/timers must return a cleanup function

3. Store Design Analysis

grep -rn "writable(\|readable(\|derived(" --include="*.ts" --include="*.js" --include="*.svelte" . 2>/dev/null | grep -v 'venv/' | head -20

# Store subscriptions without unsubscribe (leak in non-component code)
grep -rn "\.subscribe(" --include="*.ts" --include="*.js" . 2>/dev/null | head -15

# Auto-subscription in components ($store)
grep -rn '\$[a-zA-Z]' --include="*.svelte" . 2>/dev/null | grep -v '\$:\|//' | head -15

Flag:

  • Store subscriptions in .ts/.js without unsubscribe: auto-unsubscribe ($store) only works in .svelte files
  • Global stores with SSR: module-scope writable stores cause cross-request data leaks — use context or page data
  • Missing derived stores: repeated computations across components should be derived
  • Overloaded stores: single store managing unrelated concerns — split by domain

4. Performance Analysis

# Large components (>200 lines)
for f in $(find . -name "*.svelte" -not -path '*/node_modules/*' 2>/dev/null); do
  lines=$(wc -l < "$f"); if [ "$lines" -gt 200 ]; then echo "LARGE($lines): $f"; fi
done | sort -t'(' -k1.7 -rn | head -10

# Unkeyed each blocks
grep -rn '{#each' --include="*.svelte" . 2>/dev/null | grep -v '(' | head -15

# bind:this overuse
grep -rn "bind:this" --include="*.svelte" . 2>/dev/null | head -15

Flag:

  • Unkeyed each blocks: {#each items as item} without key causes full list re-renders on any change
  • Components >300 lines: decompose into sub-components
  • Excessive bind:this: direct DOM manipulation bypasses reactivity

5. Accessibility Audit

# Images without alt
grep -rn '<img' --include="*.svelte" . 2>/dev/null | grep -v 'alt=' | head -15

# Click handlers on non-interactive elements
grep -rn 'on:click' --include="*.svelte" . 2>/dev/null \
  | grep '<div\|<span\|<li\|<p' | head -15

# Form inputs without labels
grep -rn '<input' --include="*.svelte" . 2>/dev/null \
  | grep -v 'aria-label\|id=.*label\|type="hidden"\|type="submit"' | head -15

# Focus management
grep -rn 'focus()\|tabindex\|aria-live\|aria-expanded' --include="*.svelte" . 2>/dev/null | head -10

Flag with WCAG reference:

  • Images without alt: WCAG 1.1.1 — all <img> must have alt (empty for decorative)
  • Click on non-interactive elements: WCAG 4.1.2 — use <button> or add role="button" + tabindex="0" + keyboard handler
  • Inputs without labels: WCAG 1.3.1 — every form control needs an accessible name
  • No aria-live regions: WCAG 4.1.3 — dynamic status messages need aria-live="polite"

6. SSR Compatibility

# Browser-only APIs used outside onMount
grep -rn 'window\.\|document\.\|localStorage\|sessionStorage\|navigator\.' \
  --include="*.svelte" --include="*.ts" . 2>/dev/null \
  | grep -v 'node_modules\|\.svelte-kit\|onMount\|onDestroy\|browser' | head -15

# $app/environment browser guard
grep -rn "import.*browser.*\$app" --include="*.svelte" --include="*.ts" . 2>/dev/null | head -10

# Data fetching in components instead of load functions
grep -rn "fetch(" --include="*.svelte" . 2>/dev/null | grep -v 'node_modules' | head -10

# Private env leaked to client
grep -rn '\$env/static/private\|\$env/dynamic/private' --include="*.svelte" . 2>/dev/null | head -5

Flag:

  • Browser APIs outside lifecycle hooks: window, document, localStorage at top level crash SSR
  • Missing browser guard: import { browser } from $app/environment to guard browser-only code
  • Cross-request state pollution: module-level mutable state shared across SSR requests
  • Private env in client code: $env/static/private imported in .svelte files leaks secrets
  • Fetching in components: SvelteKit load functions provide streaming, caching, and SSR — component fetch loses these

7. Component API Surface

grep -rn 'export let ' --include="*.svelte" . 2>/dev/null | head -15
grep -rn 'export let [a-zA-Z]*;$' --include="*.svelte" . 2>/dev/null | head -10
grep -rn "createEventDispatcher\|setContext\|getContext" --include="*.svelte" . 2>/dev/null | head -15
grep -rn '\$\$props\|\$\$restProps' --include="*.svelte" . 2>/dev/null | head -10

Flag:

  • Props without defaults: no compile-time enforcement that callers provide them
  • $$props/$$restProps overuse: bypasses Svelte's prop tracking, prevents tree-shaking
  • Missing slot fallback content: <slot>fallback</slot> provides better DX
  • Untyped context: setContext/getContext should use typed keys

Output Template

# Svelte Component Audit — [Project Name]

## Summary
- Components: N | Svelte: 4.x/5.x | SvelteKit: yes/no
- Critical: N | Warnings: N | A11y violations: N

## Critical Findings
### [C1] SSR Crash — `window.innerWidth` at top level
- **File**: src/routes/dashboard/+page.svelte:8
- **Fix**: Move inside `onMount(() => { ... })`

### [C2] Cross-Request State Leak
- **File**: src/lib/stores/user.ts:3 — module-scope writable
- **Fix**: Initialize in load functions or context API

## Accessibility Violations
| Rule | Count | Severity | Files |
|------|-------|----------|-------|
| img-alt-missing | N | Critical | file1, file2 |
| click-on-div | N | High | file3 |
| input-no-label | N | High | file4 |

## Performance Issues
| Issue | File | Fix |
|-------|------|-----|
| Unkeyed each block | List.svelte:24 | Add `(item.id)` key |
| 450-line component | Dashboard.svelte | Split into sub-components |

## Recommendations
1. Fix N SSR-breaking browser API usages
2. Add keys to N each blocks
3. Resolve N accessibility violations
4. Move N module-scope stores to context/load for SSR safety
5. Add `+error.svelte` at route group levels

Tips

  • Run npx svelte-check for type errors, a11y warnings, and unused CSS
  • Use @sveltejs/enhanced-img for automatic image optimization in SvelteKit
  • Enable compilerOptions.runes: true in svelte.config.js to start Svelte 5 migration
  • Use {#key expression} to force-recreate a component when data changes
  • Test SSR locally with adapter-node before deploying to catch browser API issues

Version tags

latestvk972c86sbxyradgk4yz1aq15qh85x5p7