| name | performance-opt |
| description | Analyze and optimize application performance including bundle size, render performance, and API response times. Use when investigating slow pages or optimizing production builds. |
| allowed-tools | Read, Grep, Glob, Bash |
Performance Optimization Skill
This skill helps you identify and fix performance bottlenecks across the platform.
When to Use This Skill
- Investigating slow page loads
- Optimizing bundle size
- Reducing API response times
- Improving database query performance
- Optimizing React re-renders
- Before production deployments
Performance Metrics
Web Vitals (Next.js)
Core Web Vitals:
- LCP (Largest Contentful Paint): < 2.5s
- FID (First Input Delay): < 100ms
- CLS (Cumulative Layout Shift): < 0.1
Other Metrics:
- FCP (First Contentful Paint): < 1.8s
- TTFB (Time to First Byte): < 600ms
- TTI (Time to Interactive): < 3.8s
Measure Performance
# Run Lighthouse
npx lighthouse https://sgcarstrends.com --view
# Or use Chrome DevTools:
# 1. Open DevTools
# 2. Lighthouse tab
# 3. Generate report
Bundle Size Optimization
Analyze Bundle
# Next.js bundle analyzer
cd apps/web
# Add to next.config.js temporarily
ANALYZE=true pnpm build
# Or use bundle-analyzer package
pnpm add -D @next/bundle-analyzer
# View analysis
open .next/analyze/client.html
Reduce Bundle Size
1. Dynamic Imports
// ❌ Static import (loads immediately)
import { HeavyComponent } from "./heavy-component";
export default function Page() {
return <HeavyComponent />;
}
// ✅ Dynamic import (lazy load)
import dynamic from "next/dynamic";
const HeavyComponent = dynamic(() => import("./heavy-component"), {
loading: () => <div>Loading...</div>,
ssr: false, // Disable SSR if not needed
});
export default function Page() {
return <HeavyComponent />;
}
2. Tree Shaking
// ❌ Imports entire library
import _ from "lodash";
const result = _.uniq(array);
// ✅ Import only what you need
import uniq from "lodash/uniq";
const result = uniq(array);
// ✅ Or use modern alternative
const result = [...new Set(array)];
3. Optimize Dependencies
# Find large dependencies
npx npkill
# Or analyze with bundlephobia
# Visit: https://bundlephobia.com
# Replace large libs with smaller alternatives:
# moment.js (70kb) → date-fns (13kb) or dayjs (2kb)
# lodash (71kb) → lodash-es + tree shaking
4. Code Splitting
// Split by route (automatic in Next.js)
// Each page is a separate chunk
// Split by component
const AdminPanel = dynamic(() => import("./admin-panel"));
// Split by condition
const Chart = dynamic(() =>
import(userPreference === "advanced" ? "./advanced-chart" : "./simple-chart")
);
React Performance
Prevent Unnecessary Re-renders
1. useMemo
// ❌ Recalculates on every render
function Component({ data }) {
const processed = expensiveOperation(data);
return <div>{processed}</div>;
}
// ✅ Memoized calculation
function Component({ data }) {
const processed = useMemo(
() => expensiveOperation(data),
[data]
);
return <div>{processed}</div>;
}
2. useCallback
// ❌ New function on every render
function Parent() {
return <Child onClick={() => console.log("clicked")} />;
}
// ✅ Memoized function
function Parent() {
const handleClick = useCallback(() => {
console.log("clicked");
}, []);
return <Child onClick={handleClick} />;
}
3. React.memo
// ❌ Re-renders even when props unchanged
function ChildComponent({ name }) {
return <div>{name}</div>;
}
// ✅ Only re-renders when props change
const ChildComponent = React.memo(function ChildComponent({ name }) {
return <div>{name}</div>;
});
Virtualize Long Lists
# Install virtualization library
pnpm add -D react-window
import { FixedSizeList } from "react-window";
// ❌ Renders all items (slow for 1000+ items)
function CarList({ cars }) {
return (
<div>
{cars.map(car => <CarCard key={car.id} car={car} />)}
</div>
);
}
// ✅ Only renders visible items
function CarList({ cars }) {
const Row = ({ index, style }) => (
<div style={style}>
<CarCard car={cars[index]} />
</div>
);
return (
<FixedSizeList
height={600}
itemCount={cars.length}
itemSize={100}
width="100%"
>
{Row}
</FixedSizeList>
);
}
Debounce User Input
import { useDeferredValue, useState } from "react";
function SearchComponent() {
const [input, setInput] = useState("");
const deferredInput = useDeferredValue(input); // Defers update
return (
<>
<input value={input} onChange={(e) => setInput(e.target.value)} />
<SearchResults query={deferredInput} />
</>
);
}
Database Query Optimization
Identify Slow Queries
// Add query timing
const start = Date.now();
const result = await db.query.cars.findMany();
const duration = Date.now() - start;
if (duration > 100) {
console.warn(`Slow query: ${duration}ms`);
}
Optimize Queries
1. Add Indexes
// packages/database/src/db/schema/cars.ts
import { pgTable, text, index } from "drizzle-orm/pg-core";
export const cars = pgTable("cars", {
id: text("id").primaryKey(),
make: text("make").notNull(),
month: text("month").notNull(),
}, (table) => ({
// Add indexes for frequently queried columns
makeIdx: index("cars_make_idx").on(table.make),
monthIdx: index("cars_month_idx").on(table.month),
}));
2. Avoid N+1 Queries
// ❌ N+1 queries (slow)
const posts = await db.query.posts.findMany();
for (const post of posts) {
post.author = await db.query.users.findFirst({
where: eq(users.id, post.authorId),
});
}
// ✅ Single query with join (fast)
const posts = await db.query.posts.findMany({
with: {
author: true,
},
});
3. Select Only Needed Columns
// ❌ Selects all columns
const users = await db.query.users.findMany();
// ✅ Select only what's needed
const users = await db
.select({
id: users.id,
name: users.name,
email: users.email,
})
.from(users);
4. Use Pagination
// ❌ Loads all records
const cars = await db.query.cars.findMany();
// ✅ Paginated query
const cars = await db.query.cars.findMany({
limit: 20,
offset: (page - 1) * 20,
});
5. Batch Queries
// ❌ Multiple separate queries
const user1 = await db.query.users.findFirst({ where: eq(users.id, "1") });
const user2 = await db.query.users.findFirst({ where: eq(users.id, "2") });
const user3 = await db.query.users.findFirst({ where: eq(users.id, "3") });
// ✅ Single batched query
const userIds = ["1", "2", "3"];
const users = await db.query.users.findMany({
where: inArray(users.id, userIds),
});
Caching Strategies
Server-Side Caching
import { redis } from "@sgcarstrends/utils";
export async function getCarsWithCache(make: string) {
const cacheKey = `cars:${make}`;
// Check cache
const cached = await redis.get(cacheKey);
if (cached) {
return JSON.parse(cached as string);
}
// Fetch from database
const cars = await db.query.cars.findMany({
where: eq(cars.make, make),
});
// Cache for 1 hour
await redis.set(cacheKey, JSON.stringify(cars), { ex: 3600 });
return cars;
}
Next.js Caching
// Cache with revalidation
export const revalidate = 3600; // Revalidate every hour
export async function getData() {
const res = await fetch("https://api.example.com/data", {
next: { revalidate: 3600 },
});
return res.json();
}
// Cache indefinitely, revalidate on demand
export const dynamic = "force-static";
export async function getStaticData() {
// This data is cached until manually revalidated
const data = await db.query.cars.findMany();
return data;
}
Image Optimization
Use Next.js Image Component
import Image from "next/image";
// ❌ Regular img tag
<img src="/logo.png" alt="Logo" />
// ✅ Optimized image
<Image
src="/logo.png"
alt="Logo"
width={200}
height={200}
priority // For above-the-fold images
/>
// ✅ Responsive image
<Image
src="/hero.jpg"
alt="Hero"
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
priority
/>
Image Formats
// Use modern formats
<Image
src="/image.webp" // WebP for smaller size
alt="Image"
width={800}
height={600}
/>
// Automatic format optimization with Next.js Image
// Next.js automatically serves WebP/AVIF when supported
API Performance
Response Time Optimization
// Add response time logging
app.use(async (c, next) => {
const start = Date.now();
await next();
const duration = Date.now() - start;
c.header("X-Response-Time", `${duration}ms`);
if (duration > 500) {
console.warn(`Slow endpoint: ${c.req.path} (${duration}ms)`);
}
});
Compression
// Enable compression in Hono
import { compress } from "hono/compress";
const app = new Hono();
app.use("*", compress());
Pagination
// Implement cursor-based pagination
export async function getCars(cursor?: string, limit = 20) {
const query = db.query.cars.findMany({
limit: limit + 1, // Fetch one extra to determine if there's more
orderBy: desc(cars.createdAt),
});
if (cursor) {
query.where(lt(cars.id, cursor));
}
const results = await query;
const hasMore = results.length > limit;
const items = hasMore ? results.slice(0, -1) : results;
return {
items,
nextCursor: hasMore ? items[items.length - 1].id : null,
};
}
Lambda Performance
Cold Start Optimization
// infra/api.ts
export function API({ stack, app }: StackContext) {
const api = new Function(stack, "api", {
handler: "apps/api/src/index.handler",
runtime: "nodejs20.x",
architecture: "arm64", // Graviton2 for better performance
memory: 1024, // More memory = faster CPU
nodejs: {
esbuild: {
minify: true, // Smaller bundle = faster cold starts
bundle: true,
},
},
});
}
Provisioned Concurrency
For production with consistent traffic:
const api = new Function(stack, "api", {
handler: "apps/api/src/index.handler",
reservedConcurrentExecutions: 10, // Reserve instances
});
Performance Monitoring
Web Vitals in Next.js
// app/layout.tsx
import { SpeedInsights } from "@vercel/speed-insights/next";
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<SpeedInsights />
</body>
</html>
);
}
Custom Performance Marks
export async function performanceTracked Operation() {
performance.mark("operation-start");
await doSomething();
performance.mark("operation-end");
performance.measure("operation", "operation-start", "operation-end");
const measure = performance.getEntriesByName("operation")[0];
console.log(`Operation took ${measure.duration}ms`);
}
Performance Testing
Load Testing
# Install Apache Bench
# or use k6, Artillery, etc.
# Test API endpoint
ab -n 1000 -c 10 https://api.sgcarstrends.com/health
# With k6
k6 run loadtest.js
Performance Benchmarks
// __tests__/performance/database.test.ts
import { performance } from "perf_hooks";
describe("Database Performance", () => {
it("queries cars in < 100ms", async () => {
const start = performance.now();
await db.query.cars.findMany({ limit: 100 });
const duration = performance.now() - start;
expect(duration).toBeLessThan(100);
});
});
Performance Checklist
- Bundle size < 200KB (initial load)
- LCP < 2.5s
- FID < 100ms
- CLS < 0.1
- API responses < 500ms
- Database queries < 100ms
- Images optimized (WebP/AVIF)
- Code splitting implemented
- Lazy loading for heavy components
- Caching strategy in place
- Long lists virtualized
- Compression enabled
- Indexes on frequently queried columns
Quick Wins
- Enable Compression: Instant 60-80% size reduction
- Add Image Optimization: Use Next.js Image component
- Implement Caching: Cache expensive operations
- Add Indexes: Speed up database queries
- Code Splitting: Lazy load heavy components
- Tree Shaking: Import only what you need
- Memoization: Prevent unnecessary calculations
References
- Web Vitals: https://web.dev/vitals
- Next.js Performance: https://nextjs.org/docs/app/building-your-application/optimizing
- React Performance: https://react.dev/learn/render-and-commit
- Related files:
apps/web/next.config.js- Next.js configuration- Root CLAUDE.md - Performance guidelines
Best Practices
- Measure First: Use profiling before optimizing
- Focus on Impact: Optimize bottlenecks, not everything
- Monitor Production: Track real user performance
- Regular Audits: Run Lighthouse monthly
- Test Performance: Add performance tests
- Document Optimizations: Note why and what was optimized
- Avoid Premature Optimization: Profile first, then optimize
- Use Tools: Leverage built-in Next.js optimizations