| name | performance-patterns |
| description | Frontend performance patterns for Core Web Vitals (LCP, INP, CLS), Turbopack optimization, bundle splitting, Cache Components strategy, and rendering performance. Use when user mentions performance, slow loading, large bundle, Core Web Vitals, or "optimize". |
| argument-hint | [page-or-component] |
Performance Patterns Skill
Best practices for frontend performance in Next.js 16 with Turbopack, React 19.2, and modern optimization techniques.
When to Use
- User mentions "slow page" / "performance issues" / "optimize"
- Large bundle sizes or slow builds
- Core Web Vitals failing (LCP > 2.5s, INP > 200ms, CLS > 0.1)
- Questions about caching strategy, code splitting, or rendering
Quick Reference: Common Problems
| Problem | Symptom | Solution |
|---|
| Slow LCP | Large hero image, render-blocking fonts | next/image priority, next/font, Server Components |
| High INP | Laggy interactions, frozen UI | React Compiler, event delegation, Web Workers |
| Layout shift (CLS) | Content jumping on load | Image dimensions, font size-adjust, Skeleton |
| Large bundle | Slow initial load | Dynamic imports, direct imports (no barrel files) |
| Slow builds | Long CI/CD times | Turbopack, File System Cache, dependency caching |
| Over-fetching | Too much data transferred | Cache Components, Suspense streaming, pagination |
| Render waterfalls | Sequential data loading | Promise.all, parallel Suspense boundaries |
LCP Optimization
Largest Contentful Paint — target < 2.5s
Hero Image
import Image from "next/image";
export function Hero() {
return (
<Image
src="/hero.webp"
alt="Product showcase"
width={1200}
height={600}
priority // Preloads — critical for LCP
sizes="100vw"
className="w-full h-auto"
/>
);
}
Font Loading
import { Inter } from "next/font/google";
const inter = Inter({
subsets: ["latin"],
display: "swap",
variable: "--font-inter",
});
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className={inter.variable}>
<body>{children}</body>
</html>
);
}
Server Components (Zero JS for LCP)
export default async function ProductPage({ params }: Props) {
const { id } = await params;
const product = await db.product.findUnique({ where: { id } });
return (
<main>
<h1>{product.name}</h1> {/* LCP candidate — rendered on server */}
<Image src={product.image} alt={product.name} width={800} height={600} priority />
</main>
);
}
INP Optimization
Interaction to Next Paint — target < 200ms
React Compiler (Automatic Memoization)
const nextConfig = {
reactCompiler: true,
};
export default nextConfig;
Event Handler Optimization
<button onClick={() => handleClick(item.id)}>Click</button>
const worker = new Worker(new URL("./heavy-task.worker.ts", import.meta.url));
useTransition for Non-Urgent Updates
"use client";
import { useState, useTransition } from "react";
export function SearchFilter({ items }: { items: Item[] }) {
const [query, setQuery] = useState("");
const [filtered, setFiltered] = useState(items);
const [isPending, startTransition] = useTransition();
function handleSearch(value: string) {
setQuery(value);
startTransition(() => {
setFiltered(items.filter((i) => i.name.includes(value)));
});
}
return (
<div>
<input value={query} onChange={(e) => handleSearch(e.target.value)} />
<div aria-busy={isPending}>
{filtered.map((item) => <Item key={item.id} item={item} />)}
</div>
</div>
);
}
CLS Prevention
Cumulative Layout Shift — target < 0.1
Image Dimensions
<Image src="/photo.jpg" alt="Photo" width={800} height={600} />
<div className="aspect-video relative">
<Image src="/video-thumb.jpg" alt="Video" fill className="object-cover" />
</div>
Font Size Adjust
@theme {
--font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
}
Skeleton Loading
export function ProductCardSkeleton() {
return (
<div className="animate-pulse rounded-lg border p-4" aria-hidden="true">
<div className="aspect-square rounded bg-gray-200" /> {/* Image placeholder */}
<div className="mt-4 h-5 w-3/4 rounded bg-gray-200" /> {/* Title placeholder */}
<div className="mt-2 h-4 w-1/4 rounded bg-gray-200" /> {/* Price placeholder */}
</div>
);
}
Bundle Optimization
Direct Imports (Avoid Barrel Files)
import { Button } from "@/components";
import { Button } from "@/components/ui/button";
Dynamic Imports
import dynamic from "next/dynamic";
const Chart = dynamic(() => import("@/components/chart"), {
loading: () => <div className="h-64 animate-pulse rounded bg-gray-200" />,
ssr: false,
});
Dependency Analysis
import { format } from "date-fns";
import { format } from "date-fns/format";
Cache Components Strategy
export default async function DashboardPage() {
const data = await db.getData();
return <Dashboard data={data} />;
}
"use cache";
import { cacheLife } from "next/cache";
export async function PopularProducts() {
cacheLife("hours");
const products = await db.product.findMany({
orderBy: { sales: "desc" },
take: 10,
});
return <ProductGrid products={products} />;
}
When to Cache
| Scenario | Strategy |
|---|
| User-specific data (dashboard) | Dynamic (default, no cache) |
| Product catalog | Cache Component ("use cache" + cacheLife("hours")) |
| Blog posts | Cache Component ("use cache" + cacheLife("days")) |
| Marketing pages | Cache Component ("use cache" + cacheLife("max")) |
| Real-time data (notifications) | Dynamic + client polling (TanStack Query) |
| Form submission result | updateTag() for instant feedback |
Streaming with Suspense
import { Suspense } from "react";
export default function DashboardPage() {
return (
<div>
<h1>Dashboard</h1>
{/* Fast data — streams first */}
<Suspense fallback={<StatsSkeleton />}>
<QuickStats />
</Suspense>
{/* Slow data — streams when ready, doesn't block above */}
<Suspense fallback={<ChartSkeleton />}>
<RevenueChart />
</Suspense>
<Suspense fallback={<TableSkeleton />}>
<RecentOrders />
</Suspense>
</div>
);
}
Performance Budget (CI)
- name: Lighthouse CI
uses: treosh/lighthouse-ci-action@v12
with:
urls: |
http://localhost:3000/
http://localhost:3000/products
budgetPath: ./budget.json
uploadArtifacts: true
[{
"path": "/*",
"timings": [
{ "metric": "largest-contentful-paint", "budget": 2500 },
{ "metric": "interactive", "budget": 3500 },
{ "metric": "cumulative-layout-shift", "budget": 0.1 }
],
"resourceSizes": [
{ "resourceType": "script", "budget": 200 },
{ "resourceType": "total", "budget": 500 }
]
}]
Monitoring
import { SpeedInsights } from "@vercel/speed-insights/next";
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<SpeedInsights />
</body>
</html>
);
}
Quick Checklist
| Category | Check |
|---|
| LCP | Hero image has priority, fonts via next/font, Server Components |
| INP | React Compiler enabled, useTransition for heavy filters |
| CLS | Image width/height set, font size-adjust, skeleton loading |
| Bundle | No barrel files, dynamic imports for heavy components |
| Network | Suspense streaming, parallel fetching, Cache Components |
| Build | Turbopack enabled (default), File System Cache, CI caching |
| Monitoring | web-vitals/SpeedInsights, Lighthouse CI budget |