performance-budget

Performance budgets — bundle size limits, LCP/FID/CLS targets, lighthouse CI, size-limit, bundlephobia

39 stars

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

$curl -o ~/.claude/skills/performance-budget/SKILL.md --create-dirs "https://raw.githubusercontent.com/InugamiDev/ultrathink-oss/main/.claude/skills/performance-budget/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/performance-budget/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How performance-budget Compares

Feature / Agentperformance-budgetStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/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

39
from InugamiDev/ultrathink-oss

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

39
from InugamiDev/ultrathink-oss

Profile, benchmark, and identify performance bottlenecks in applications — CPU, memory, network, rendering, and database query performance

context-budget

39
from InugamiDev/ultrathink-oss

Token overhead audit for UltraThink context window. Inventories skills, MCPs, hooks, and rules to detect bloat and optimize token usage.

ultrathink

39
from InugamiDev/ultrathink-oss

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

39
from InugamiDev/ultrathink-oss

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

39
from InugamiDev/ultrathink-oss

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

39
from InugamiDev/ultrathink-oss

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

39
from InugamiDev/ultrathink-oss

> TypeScript-first schema validation with static type inference.

webinar-registration-page

39
from InugamiDev/ultrathink-oss

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

39
from InugamiDev/ultrathink-oss

Webhook design patterns — delivery, retry with exponential backoff, HMAC signature verification, payload validation, idempotency keys

web-workers

39
from InugamiDev/ultrathink-oss

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

39
from InugamiDev/ultrathink-oss

Core Web Vitals monitoring (LCP, FID, CLS, INP, TTFB), measurement with web-vitals library, reporting to analytics, and optimization strategies for Next.js