performance-budget
Performance budgets — bundle size limits, LCP/FID/CLS targets, lighthouse CI, size-limit, bundlephobia
Best use case
performance-budget is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Performance budgets — bundle size limits, LCP/FID/CLS targets, lighthouse CI, size-limit, bundlephobia
Teams using performance-budget should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/performance-budget/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How performance-budget Compares
| Feature / Agent | performance-budget | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Performance budgets — bundle size limits, LCP/FID/CLS targets, lighthouse CI, size-limit, bundlephobia
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Performance Budgets
## Purpose
Provide expert guidance on defining, measuring, and enforcing performance budgets for web applications. Covers bundle size limits with size-limit, Core Web Vitals targets, Lighthouse CI integration, and CI pipeline enforcement to prevent performance regressions before they reach production.
## Key Patterns
### Defining Performance Budgets
Start with targets based on user impact, not arbitrary numbers.
**Core Web Vitals targets (Good threshold):**
| Metric | Target | What It Measures |
|--------|--------|-----------------|
| LCP (Largest Contentful Paint) | < 2.5s | Loading performance |
| INP (Interaction to Next Paint) | < 200ms | Responsiveness |
| CLS (Cumulative Layout Shift) | < 0.1 | Visual stability |
| FCP (First Contentful Paint) | < 1.8s | Perceived load speed |
| TTFB (Time to First Byte) | < 800ms | Server responsiveness |
**Bundle size budgets (recommended starting points):**
| Resource | Budget | Rationale |
|----------|--------|-----------|
| Initial JS (compressed) | < 150 KB | Parse/execute time on mobile |
| Initial CSS | < 50 KB | Render-blocking resource |
| Total page weight | < 500 KB | 3G load time under 5s |
| Largest single chunk | < 100 KB | Avoid dominating the bundle |
| Images per page | < 500 KB | Largest weight contributor |
### size-limit Configuration
Enforce bundle size budgets in CI with `size-limit`.
```json
// package.json
{
"scripts": {
"size": "size-limit",
"size:check": "size-limit --json"
},
"devDependencies": {
"size-limit": "^11.0.0",
"@size-limit/preset-app": "^11.0.0"
}
}
```
```javascript
// .size-limit.js
module.exports = [
{
name: "Initial JS",
path: ".next/static/chunks/**/*.js",
limit: "150 KB",
gzip: true,
},
{
name: "Shared UI package",
path: "packages/ui/dist/index.js",
limit: "30 KB",
gzip: true,
import: "{ Button, Card, Input }",
},
{
name: "Utils package",
path: "packages/utils/dist/index.js",
limit: "10 KB",
gzip: true,
},
{
name: "CSS bundle",
path: ".next/static/css/**/*.css",
limit: "50 KB",
gzip: true,
},
];
```
**For library packages (tree-shaking verification):**
```javascript
// .size-limit.js for a UI library
module.exports = [
{
name: "Full bundle",
path: "dist/index.js",
limit: "45 KB",
},
{
name: "Button only (tree-shaking)",
path: "dist/index.js",
import: "{ Button }",
limit: "5 KB",
},
{
name: "Card only (tree-shaking)",
path: "dist/index.js",
import: "{ Card }",
limit: "3 KB",
},
];
```
### Lighthouse CI
Automate Lighthouse audits in CI to catch performance regressions.
```json
// lighthouserc.json
{
"ci": {
"collect": {
"url": [
"http://localhost:3000/",
"http://localhost:3000/products",
"http://localhost:3000/checkout"
],
"startServerCommand": "pnpm start",
"startServerReadyPattern": "ready on",
"numberOfRuns": 3,
"settings": {
"preset": "desktop",
"throttling": {
"cpuSlowdownMultiplier": 2
}
}
},
"assert": {
"assertions": {
"categories:performance": ["error", { "minScore": 0.9 }],
"categories:accessibility": ["error", { "minScore": 0.95 }],
"categories:best-practices": ["warn", { "minScore": 0.9 }],
"categories:seo": ["warn", { "minScore": 0.9 }],
"first-contentful-paint": ["error", { "maxNumericValue": 1800 }],
"largest-contentful-paint": ["error", { "maxNumericValue": 2500 }],
"cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }],
"total-blocking-time": ["error", { "maxNumericValue": 300 }],
"interactive": ["warn", { "maxNumericValue": 3500 }]
}
},
"upload": {
"target": "temporary-public-storage"
}
}
}
```
**GitHub Actions integration:**
```yaml
name: Performance Budget
on:
pull_request:
branches: [main]
jobs:
bundle-size:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm build
- name: Check bundle size
run: pnpm size
- name: Bundle size report
uses: andresz1/size-limit-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
build_script: ""
skip_step: build
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm build
- name: Run Lighthouse CI
run: |
npm install -g @lhci/cli
lhci autorun
- name: Comment results on PR
if: github.event_name == 'pull_request'
uses: treosh/lighthouse-ci-action@v12
with:
configPath: ./lighthouserc.json
uploadArtifacts: true
```
### Next.js Bundle Analysis
```bash
# Install the analyzer
pnpm add -D @next/bundle-analyzer
```
```javascript
// next.config.js
import bundleAnalyzer from "@next/bundle-analyzer";
const withBundleAnalyzer = bundleAnalyzer({
enabled: process.env.ANALYZE === "true",
});
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
optimizePackageImports: [
"lucide-react",
"@radix-ui/react-icons",
"date-fns",
"lodash-es",
],
},
};
export default withBundleAnalyzer(nextConfig);
```
### Custom Budget Enforcement Script
For more granular control than size-limit provides:
```typescript
// scripts/check-budget.ts
import { readFileSync, readdirSync } from "fs";
import { join } from "path";
import { gzipSync } from "zlib";
interface Budget {
name: string;
pattern: RegExp;
maxSize: number; // bytes, gzipped
}
const budgets: Budget[] = [
{ name: "Framework JS", pattern: /framework-.*\.js$/, maxSize: 50_000 },
{ name: "App JS", pattern: /app\/.*\.js$/, maxSize: 100_000 },
{ name: "CSS total", pattern: /\.css$/, maxSize: 50_000 },
];
function getGzipSize(filePath: string): number {
const content = readFileSync(filePath);
return gzipSync(content).length;
}
function walkDir(dir: string): string[] {
const files: string[] = [];
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...walkDir(fullPath));
} else {
files.push(fullPath);
}
}
return files;
}
const buildDir = ".next/static";
const allFiles = walkDir(buildDir);
let failed = false;
for (const budget of budgets) {
const matchingFiles = allFiles.filter((f) => budget.pattern.test(f));
const totalSize = matchingFiles.reduce((sum, f) => sum + getGzipSize(f), 0);
const status = totalSize > budget.maxSize ? "FAIL" : "PASS";
const sizeKB = (totalSize / 1024).toFixed(1);
const limitKB = (budget.maxSize / 1024).toFixed(1);
console.log(`${status}: ${budget.name} - ${sizeKB} KB / ${limitKB} KB`);
if (totalSize > budget.maxSize) {
failed = true;
}
}
if (failed) {
console.error("\nBundle budget exceeded. Run `pnpm analyze` to investigate.");
process.exit(1);
}
```
### Monitoring in Production
```typescript
// Report Core Web Vitals to your analytics endpoint
import { onCLS, onINP, onLCP, onFCP, onTTFB } from "web-vitals";
function sendToAnalytics(metric: { name: string; value: number; id: string }) {
navigator.sendBeacon(
"/api/vitals",
JSON.stringify({
name: metric.name,
value: metric.value,
id: metric.id,
url: location.href,
timestamp: Date.now(),
})
);
}
onCLS(sendToAnalytics);
onINP(sendToAnalytics);
onLCP(sendToAnalytics);
onFCP(sendToAnalytics);
onTTFB(sendToAnalytics);
```
## Best Practices
- **Set budgets based on user scenarios** -- a 3G connection in an emerging market needs stricter budgets than desktop broadband.
- **Enforce budgets in CI as blocking checks** -- performance regressions are much harder to fix after merging.
- **Use gzip size for budget comparisons** -- raw size is misleading because compression varies by content type.
- **Run Lighthouse on multiple pages** -- the homepage is often light; product pages and checkout may be heavy.
- **Track budgets over time** -- use Lighthouse CI server or a dashboard to spot trends before they breach limits.
- **Audit npm dependencies regularly** -- a single dependency update can add 50KB+ to your bundle.
- **Use `optimizePackageImports`** in Next.js for barrel-file-heavy libraries to ensure tree shaking works.
## Common Pitfalls
| Pitfall | Problem | Fix |
|---------|---------|-----|
| Budget on raw size, not gzip | Misleading numbers; 500KB raw may be 80KB gzipped | Always measure and budget gzip (or brotli) size |
| No budget on per-route bundles | Home page is fine but product page loads 500KB of charts | Add per-route or per-chunk budgets |
| Lighthouse on localhost only | No network latency or real-world conditions | Use throttling profiles or test against deployed preview URLs |
| Ignoring third-party scripts | Analytics, chat widgets, ads add 200KB+ | Include third-party scripts in budget; lazy-load non-essential ones |
| Setting budgets too loose | Budget never fails, regressions accumulate | Start tight and adjust -- a budget that never triggers is useless |
| Not analyzing what changed | Budget fails but no context on why | Use `size-limit-action` PR comments or `@next/bundle-analyzer` |Related Skills
performance-report
Generate affiliate performance reports with KPIs and recommendations. Triggers on: "show my affiliate report", "how are my programs doing", "performance review", "earnings report", "monthly affiliate report", "weekly report", "analyze my affiliate earnings", "which program is best", "EPC report", "conversion rate analysis", "revenue breakdown", "campaign performance".
performance-profiler
Profile, benchmark, and identify performance bottlenecks in applications — CPU, memory, network, rendering, and database query performance
context-budget
Token overhead audit for UltraThink context window. Inventories skills, MCPs, hooks, and rules to detect bloat and optimize token usage.
ultrathink
UltraThink Workflow OS — 4-layer skill mesh with persistent memory and privacy hooks for complex engineering tasks. Routes prompts through intent detection to activate the right domain skills automatically.
ultrathink_review
Multi-pass code review powered by UltraThink's quality gate — checks correctness, security (OWASP), performance, readability, and project conventions in a single structured pass.
ultrathink_memory
Persistent memory system for UltraThink — search, save, and recall project context, decisions, and patterns across sessions using Postgres-backed fuzzy search with synonym expansion.
ui-design
Comprehensive UI design system: 230+ font pairings, 48 themes, 65 design systems, 23 design languages, 30 UX laws, 14 color systems, Swiss grid, Gestalt principles, Pencil.dev workflow. Inherits ui-ux-pro-max (99 UX rules) + impeccable-frontend-design (anti-AI-slop). Triggers on any design, UI, layout, typography, color, theme, or styling task.
Zod
> TypeScript-first schema validation with static type inference.
webinar-registration-page
Build a webinar or live event registration page as a self-contained HTML file with countdown timer, speaker bio, agenda, and registration form. Triggers on: "build a webinar registration page", "create a webinar sign-up page", "event registration landing page", "live training registration page", "workshop sign-up page", "create a webinar page", "build an event page", "free webinar landing page", "live demo registration page", "online event page", "create a registration page for my webinar", "build a training event page".
webhooks
Webhook design patterns — delivery, retry with exponential backoff, HMAC signature verification, payload validation, idempotency keys
web-workers
Offload heavy computation from the main thread using Web Workers, SharedWorkers, and Comlink — structured messaging, transferable objects, and off-main-thread architecture patterns
web-vitals
Core Web Vitals monitoring (LCP, FID, CLS, INP, TTFB), measurement with web-vitals library, reporting to analytics, and optimization strategies for Next.js