perf-optimizer
Skill6 starsChanged 8 months agoMITView on GitHub
Optimize frontend performance in Next.js/React applications — Core Web Vitals, bundle size reduction, lazy loading, image optimization, code splitting, caching strategies, and rendering performance. Use this skill whenever the user mentions performance, page speed, slow loading, Core Web Vitals, LCP, CLS, INP, bundle size, code splitting, lazy loading, memoization, or wants to make their app faster. Also trigger when the user asks about optimizing images, reducing JavaScript, improving Time to Interactive, or diagnosing rendering bottlenecks.
- Reads credentials
- Installs packages
2 other projects use a version of this.
Adopted by
Most recently taken up by
- codetesla51/skillstheir own version, first seen Nov 1, 2026
- tusharagg1/aifolimizertheir own version, first seen Jun 11, 2026
Other versions2 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 2
- codetesla51/skills★ 32same name
- tusharagg1/aifolimizer★ 2same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository20 other files it gives its agents.
- a11y-auditSkill
- design-an-interfaceSkill
- edit-articleSkill
- find-skillsSkill
- grill-meSkill
- improve-codebase-architectureSkill
- nextjs-architectSkill
- prd-to-issuesSkill
- react-patternsSkill
- request-refactor-planSkill
- seo-auditSkill
- solana-devSkill
- tddSkill
- triage-issueSkill
- write-a-prdSkill
- write-a-skillSkill
- docxSkill
- frontend-designSkill
- mcp-builderSkill
- skill-creatorSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
13 rules. About 0 are common, 4 are distinctive, and 9 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈4
Distinctive · estimate
This author's own choices. Worth a look.
9
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 122
Don't load components the user might never see (modals, charts, editors) upfront:
- Very specific: line 196
Use import { specific } not import *
- Very specific: line 197
Check that libraries export ESM (look for "module" field in their package.json)
- Distinctive: line 198
Avoid side effects in module scope
- Distinctive: line 405
Keep pages static whenever possible — they're served from CDN and are the fastest option.
- Distinctive: line 409
When a user asks "make my app faster," start with these high-impact items:
- Very specific: line 411
Add priority to the LCP image
- Very specific: line 412
Use next/image for all images with proper sizes
- Very specific: line 413
Use next/font instead of <link> to Google Fonts
- Very specific: line 415
Add loading.tsx skeletons for async pages
- Very specific: line 417
Check bundle with ANALYZE=true npm run build — look for obvious bloat
- Very specific: line 418
Use revalidate on fetch calls instead of no-store where possible
- Very specific: line 420
Ensure no useEffect fetching in client components — move to server components
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Frontend Performance Optimization
Help users diagnose and fix performance issues in Next.js/React applications with Tailwind CSS. Focus on high-impact changes rather than micro-optimizations.
Core Web Vitals
Google's Core Web Vitals are the three metrics that matter most for real-world user experience:
| Metric | What it measures | Good | Needs work | Poor |
| LCP (Largest Contentful Paint) | Loading — how fast the main content appears | < 2.5s | 2.5–4s | > 4s |
| INP (Interaction to Next Paint) | Responsiveness — how fast the page responds to input | < 200ms | 200–500ms | > 500ms |
| CLS (Cumulative Layout Shift) | Visual stability — how much the page jumps around | < 0.1 | 0.1–0.25 | > 0.25 |
Measuring performance
# Next.js built-in analytics
# Add to next.config.js:
# experimental: { webVitals: true }
# Or use the web-vitals library
npm install web-vitals// app/layout.tsx — report Web Vitals
export function reportWebVitals(metric: {
id: string
name: string
value: number
}) {
console.log(metric) // or send to analytics
}Browser tools:
- Lighthouse (Chrome DevTools > Lighthouse tab) — synthetic lab test
- Chrome DevTools Performance tab — detailed trace of what's happening
- PageSpeed Insights — real-world field data from Chrome users
Image Optimization
Images are typically the largest elements on a page and the biggest opportunity for improvement. Next.js has a built-in Image component that handles optimization automatically.
Use next/image
import Image from 'next/image'
// Local images — automatically optimized at build time
import heroImage from '@/public/hero.jpg'
export function Hero() {
return (
<Image
src={heroImage}
alt="Product dashboard showing analytics"
priority // LCP image — preload it
placeholder="blur" // show blur while loading (local images only)
className="rounded-lg"
/>
)
}
// Remote images — optimized on-demand
export function Avatar({ user }: { user: User }) {
return (
<Image
src={user.avatarUrl}
alt={`${user.name}'s avatar`}
width={40}
height={40}
className="rounded-full"
/>
)
}Key rules
- Always set
priorityon the LCP image — the hero image, main product photo, or whatever is the largest visible element above the fold. This tells Next.js to preload it. - Always provide width and height (or use
fill) — this prevents layout shift (CLS) because the browser knows the space to reserve before the image loads. - Use
fillfor responsive containers:
<div className="relative aspect-video">
<Image
src="/banner.jpg"
alt="Banner"
fill
className="object-cover rounded-lg"
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
</div>- Provide
sizeswhen usingfillor responsive layouts — this tells the browser which size to download instead of always fetching the largest version:
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"- Configure remote domains in
next.config.js:
images: {
remotePatterns: [
{ protocol: 'https', hostname: 'cdn.example.com' },
],
},Code Splitting and Lazy Loading
Dynamic imports for heavy components
Don't load components the user might never see (modals, charts, editors) upfront:
import dynamic from 'next/dynamic'
// Heavy chart library — only load when rendered
const Chart = dynamic(() => import('@/components/chart'), {
loading: () => <div className="h-64 bg-gray-100 animate-pulse rounded-lg" />,
})
// Modal — only load when opened
const SettingsModal = dynamic(() => import('@/components/settings-modal'))
export function Dashboard() {
const [showSettings, setShowSettings] = useState(false)
return (
<>
<Chart data={chartData} />
<button onClick={() => setShowSettings(true)}>Settings</button>
{showSettings && <SettingsModal onClose={() => setShowSettings(false)} />}
</>
)
}Disable SSR for client-only components
Some libraries (maps, rich text editors, canvas-based tools) crash during server rendering:
const Map = dynamic(() => import('@/components/map'), {
ssr: false,
loading: () => <div className="h-96 bg-gray-100 animate-pulse rounded-lg" />,
})Route-based splitting
Next.js automatically code-splits by route — each page only loads its own JavaScript. This works out of the box with the App Router.
For large shared components used on only some pages, move them out of the shared layout and import them only in the pages that need them.
Bundle Size
Analyze your bundle
# Install the analyzer
npm install -D @next/bundle-analyzer
# next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
})
module.exports = withBundleAnalyzer(nextConfig)
# Run analysis
ANALYZE=true npm run buildCommon bundle bloaters and fixes
| Problem | Fix |
Full lodash imported | Use lodash/debounce instead of import { debounce } from 'lodash' |
moment.js (330KB) | Switch to date-fns (tree-shakeable) or native Intl APIs |
| Icon libraries (full set) | Import individual icons: import { Search } from 'lucide-react' |
| Unused dependencies | Run npx depcheck to find them |
| Large date/number formatting | Use native Intl.DateTimeFormat and Intl.NumberFormat |
Tree shaking
ES modules are tree-shakeable — unused exports are removed at build time. Ensure:
- Use
import { specific }notimport * - Check that libraries export ESM (look for
"module"field in their package.json) - Avoid side effects in module scope
Rendering Performance
Avoid unnecessary re-renders
React re-renders a component when its state changes or its parent re-renders. Most re-renders are fine — React is fast. Only optimize when you measure an actual problem.
When to reach for memoization:
// useMemo — expensive computation
const sortedItems = useMemo(
() => items.sort((a, b) => a.name.localeCompare(b.name)),
[items]
)
// useCallback — stable function reference for child components
const handleSelect = useCallback((id: string) => {
setSelectedId(id)
}, [])
// React.memo — skip re-render when props haven't changed
const ExpensiveList = memo(function ExpensiveList({ items }: { items: Item[] }) {
return items.map(item => <ExpensiveRow key={item.id} item={item} />)
})Don't memoize everything. Memoization has its own cost (memory, comparison checks). Use it when:
- A component renders frequently with the same props
- A computation is genuinely expensive (sorting/filtering large lists, complex calculations)
- A callback is passed to many child components or used in a dependency array
Virtualize long lists
For lists with hundreds or thousands of items, render only what's visible:
npm install @tanstack/react-virtual"use client"
import { useVirtualizer } from '@tanstack/react-virtual'
import { useRef } from 'react'
function VirtualList({ items }: { items: Item[] }) {
const parentRef = useRef<HTMLDivElement>(null)
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 64,
})
return (
<div ref={parentRef} className="h-96 overflow-auto">
<div style={{ height: virtualizer.getTotalSize(), position: 'relative' }}>
{virtualizer.getVirtualItems().map(virtualRow => (
<div
key={virtualRow.key}
className="absolute w-full px-4 py-3 border-b"
style={{
height: virtualRow.size,
transform: `translateY(${virtualRow.start}px)`,
}}
>
{items[virtualRow.index].name}
</div>
))}
</div>
</div>
)
}Loading Strategies
Streaming with Suspense
Break pages into independent sections that load in parallel. Fast sections appear immediately while slower ones stream in:
import { Suspense } from 'react'
export default function DashboardPage() {
return (
<div className="grid grid-cols-12 gap-6 p-6">
{/* Fast — renders immediately */}
<div className="col-span-12">
<h1 className="text-2xl font-bold">Dashboard</h1>
</div>
{/* Each section loads independently */}
<div className="col-span-8">
<Suspense fallback={<ChartSkeleton />}>
<RevenueChart />
</Suspense>
</div>
<div className="col-span-4">
<Suspense fallback={<ListSkeleton />}>
<RecentOrders />
</Suspense>
</div>
<div className="col-span-12">
<Suspense fallback={<TableSkeleton />}>
<ActivityTable />
</Suspense>
</div>
</div>
)
}Prefetching
Next.js prefetches linked routes automatically when <Link> elements are visible in the viewport. For programmatic prefetching:
"use client"
import { useRouter } from 'next/navigation'
function ProductCard({ product }: { product: Product }) {
const router = useRouter()
return (
<div
onMouseEnter={() => router.prefetch(`/products/${product.id}`)}
className="p-4 border rounded-lg hover:shadow-md transition-shadow cursor-pointer"
onClick={() => router.push(`/products/${product.id}`)}
>
{product.name}
</div>
)
}Fonts
Use next/font to avoid layout shift
// app/layout.tsx
import { Inter } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
display: 'swap', // show fallback font immediately, swap when loaded
variable: '--font-inter',
})
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className={inter.variable}>
<body className="font-sans">{children}</body>
</html>
)
}// tailwind.config.js
module.exports = {
theme: {
extend: {
fontFamily: {
sans: ['var(--font-inter)', ...defaultTheme.fontFamily.sans],
},
},
},
}next/font self-hosts fonts with zero layout shift — no external requests to Google Fonts, no FOUT/FOIT.
Caching
Next.js caching layers
- Request Memoization — duplicate
fetch()calls in the same render pass are automatically deduplicated - Data Cache —
fetch()responses are cached on the server across requests - Full Route Cache — static routes are pre-rendered at build time
- Router Cache — visited routes are cached in the browser for instant back/forward navigation
Revalidation strategies
// Time-based — revalidate every hour
fetch(url, { next: { revalidate: 3600 } })
// On-demand — revalidate when data changes
// In a Server Action or API route:
import { revalidatePath, revalidateTag } from 'next/cache'
revalidatePath('/products') // revalidate a specific path
revalidateTag('products') // revalidate all fetches tagged 'products'Static vs dynamic rendering
Pages are statically rendered by default. A page becomes dynamic when it uses:
cookies(),headers(),searchParamsfetch()with{ cache: 'no-store' }export const dynamic = 'force-dynamic'
Keep pages static whenever possible — they're served from CDN and are the fastest option.
Quick Wins Checklist
When a user asks "make my app faster," start with these high-impact items:
- Add
priorityto the LCP image - Use
next/imagefor all images with propersizes - Use
next/fontinstead of<link>to Google Fonts - Dynamic import heavy components (charts, editors, modals)
- Add
loading.tsxskeletons for async pages - Wrap independent data-fetching sections in
<Suspense> - Check bundle with
ANALYZE=true npm run build— look for obvious bloat - Use
revalidateon fetch calls instead ofno-storewhere possible - Virtualize lists with 100+ items
- Ensure no
useEffectfetching in client components — move to server components
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

