react-performance
Skill0 starsChanged 5 months agoMITView on GitHub
React performance and Core Web Vitals — LCP, INP, CLS, bundle size, lazy loading, code splitting, memoization patterns (useMemo, useCallback, React.memo), image optimization, font optimization, when NOT to optimize. Use when optimizing slow components, debugging performance, building for performance-critical pages (marketing/landing), or auditing Core Web Vitals. Triggers on "performance", "slow", "Core Web Vitals", "LCP", "INP", "CLS", "bundle", "lazy", "memoize", "image optimization".
- Reads credentials
18 other projects use a version of this.
Adopted by
Most recently taken up by
- Lu1sDV/skillsmdtheir own version, first seen Nov 8, 2026
- ahmadvahidi/skill-atlastheir own version, first seen Nov 8, 2026
- conjurer-rich/skillstheir own version, first seen Oct 25, 2026
- renepf/claude-skillstheir own version, first seen Oct 25, 2026
- davidanthonyn/claude-skillstheir own version, first seen Oct 20, 2026
- Ronnasayd/aipptheir own version, first seen Oct 20, 2026
Used with
Projects with it also use orch-pipeline, parallel-execution-optimizer and 10 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
renepfSkillorch-pipeline
Shared orchestration engine for the orch-* skill family. Defines the gated Research-Plan-TDD-Review-Commit pipeline, the size classifier, the agent map, and the two human gates that the orch-* operation skills delegate to. Not usually invoked directly.
Also in 3 projects that use this
marcusgollSkillparallel-execution-optimizer
Identify and execute independent operations in parallel for 3-5x speedup. Auto-analyzes task dependencies, groups into batches, launches parallel Task() calls. Applies to /optimize (5 checks), /ship pre-flight (5 checks), /implement (task batching), /prototype (N screens). Auto-triggers when detecting multiple independent operations in a phase.
Also in 4 projects that use this
ronmkrSkillvite-patterns
Vite build tool patterns including config, plugins, HMR, env variables, proxy setup, SSR, library mode, dependency pre-bundling, and build optimization. Activate when working with vite.config.ts, Vite plugins, or Vite-based projects.
Also in 4 projects that use this
RojiosSkilldefi-amm-security
Security checklist for Solidity AMM contracts, liquidity pools, and swap flows. Covers reentrancy, CEI ordering, donation or inflation attacks, oracle manipulation, slippage, admin controls, and integer math.
Also in 3 projects that use this
RojiosSkillsecurity-bounty-hunter
Hunt for exploitable, bounty-worthy security issues in repositories. Focuses on remotely reachable vulnerabilities that qualify for real reports instead of noisy local-only findings.
Also in 4 projects that use this
renepfSkillplan-orchestrate
Read a plan document, decompose it into steps, design a per-step agent chain from the ECC catalogue, and emit ready-to-paste /orchestrate custom prompts. Generative only — never invokes /orchestrate itself. Use when the user has a multi-step plan and wants to drive it through orchestrate without composing chains by hand.
Also in 3 projects that use this
Other versions18 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 18
- s-hiraoku/synapse-a2a★ 17same name
- wpank/ai★ 11same name
- gongyijie85/dsh-ecc★ 8same name
- jscott3201/yet-more-skills★ 4same name
- nek1987/auto-agent-harness★ 4same name
- LeahyCC/claude-skills★ 3same name
- Lu1sDV/skillsmd★ 2same name
- badri-ravi/skills★ 1same name
- conjurer-rich/skills★ 1same name
- Evan-Kim2028/agent-skills★ 1same name
8 more not listed
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.
- react-a11ySkill
- react-api-consumerSkill
- react-bootstrapSkill
- react-brief-recipesSkill
- react-component-designSkill
- react-docs-writingSkill
- react-embedsSkill
- react-forms-validationSkill
- react-html-platformSkill
- react-interview-async-slackSkill
- react-interview-narrationSkill
- react-marketing-patternsSkill
- react-modern-reactSkill
- react-onboarding-docsSkill
- react-resourcesSkill
- react-routingSkill
- react-senior-uxSkill
- react-stylingSkill
- react-testingSkill
- react-utility-snippetsSkill
The file
We split this skill into the rules it gives an agent
6 rules. 0 are common, 3 are distinctive, and 3 are very specific to this project.
0
Common
Most of the 18 edited versions we read keep these.
3
Distinctive
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 12
When asked to "make this faster", check in this order.
- Distinctive: line 19
Then: look at React-specific concerns (re-renders, expensive components)
- Very specific: line 156
Use @tanstack/react-virtual or react-window for any list > 100 items.
- Distinctive: line 322
Don't bother with the historical 47-link-tag favicon dance unless you're shipping to a true legacy audience.
- Very specific: line 356
await document.fonts.ready: before screenshotting if you must use a custom font. - Very specific: line 358
Don't try to inline a @font-face declaration in the OG template — the screenshotter's headless browser still needs to download the font …
“Common” is measured: kept by at least 6 in 10 of the 18 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
react-performance
Senior performance work is mostly not about React. It's about images, fonts, layout shift, and not shipping JS users don't need. Optimize React last.
The performance triage order
When asked to "make this faster", check in this order. The first three usually fix 80%.
- Images — are they sized, lazy-loaded below the fold, modern formats?
- Fonts — are they self-hosted, preloaded,
font-display: swap? - Layout shift — do images/ads have reserved space?
- Bundle size — what's in the JS? Code split anything route-bound?
- Third-party scripts — what's blocking?
- Then look at React-specific concerns (re-renders, expensive components)
Mid-level engineers reach for React.memo first. Senior engineers reach for <Image priority>.
Core Web Vitals (memorize these thresholds)
| Metric | What it measures | Good | Needs work |
| LCP (Largest Contentful Paint) | Time to render the biggest above-fold element | ≤ 2.5s | > 4s |
| INP (Interaction to Next Paint) | Latency on user interactions (replaces FID in 2024) | ≤ 200ms | > 500ms |
| CLS (Cumulative Layout Shift) | How much things visually jump | ≤ 0.1 | > 0.25 |
Plus useful supplements:
- FCP (First Contentful Paint): first text/image, ≤ 1.8s
- TTFB (Time to First Byte): server response, ≤ 0.8s
- TBT (Total Blocking Time): main-thread blocked during load, ≤ 200ms
LCP — make the hero render fast
The LCP element is usually the hero image, hero heading, or hero video. Speed it up:
Image LCP fixes
// Next.js
import Image from 'next/image';
<Image
src="/hero.jpg"
alt="Build smarter, ship faster"
width={1200}
height={630}
priority // preload, skip lazy
fetchPriority="high"
sizes="100vw" // or specific sizes per breakpoint
/>Plain HTML equivalent:
<link rel="preload" as="image" href="/hero.avif" type="image/avif" fetchpriority="high" />
<picture>
<source type="image/avif" srcset="/hero.avif" />
<source type="image/webp" srcset="/hero.webp" />
<img
src="/hero.jpg"
alt="…"
width="1200"
height="630"
fetchpriority="high"
decoding="async"
/>
</picture>- Always width/height (prevents CLS)
- Modern formats: AVIF, WebP fallback to JPEG
- Responsive sizes:
srcset+sizesso phones don't download desktop images - Below-the-fold:
loading="lazy"(default off for above-the-fold) - Don't lazy-load the LCP image — it should be eager + priority
Font LCP fixes
// Next.js — self-hosts and inlines optimally
import { Inter } from 'next/font/google';
const inter = Inter({ subsets: ['latin'], display: 'swap' });Plain HTML:
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="preload" as="font" type="font/woff2" href="/fonts/inter-var.woff2" crossorigin />@font-face {
font-family: 'Inter';
src: url('/fonts/inter-var.woff2') format('woff2-variations');
font-weight: 100 900;
font-display: swap; /* render with fallback first, swap when loaded */
}- Self-host WOFF2 — eliminates a third-party DNS hop
- Variable fonts — one file for all weights
font-display: swap— render text immediately with fallback- Subset to needed characters (Latin only if your audience is Latin)
Render-blocking resources
- Move non-critical CSS out of the critical path
- Defer third-party scripts:
<script defer>or<script async>(defer is usually right) - Inline critical CSS if you're squeezing TTFB
INP — keep interactions snappy
INP is the new FID. It measures the worst event handler delay over a session.
Top INP fixes
- Don't do expensive work in event handlers
// Bad — re-filters 10k items on every keystroke, then renders onChange={e => setQuery(e.target.value)} // OK // but if filtering happens during render and it's heavy → INP suffers // Fix: useDeferredValue const deferredQuery = useDeferredValue(query); const results = useMemo(() => filterHeavy(items, deferredQuery), [items, deferredQuery]); - Break up long tasks
// Yield to the browser between batches async function processChunked(items: Item[]) { for (let i = 0; i < items.length; i += 100) { processBatch(items.slice(i, i + 100)); await new Promise(r => setTimeout(r, 0)); // yields } } - Use CSS for visual feedback first
CSS feedback is instant; JS work can come after.<button className="active:scale-95 transition-transform">…</button> startTransitionfor non-urgent updatesconst [isPending, startTransition] = useTransition(); onClick={() => { startTransition(() => { setBigStateUpdate(); // marked as low-priority }); }}- Virtualize long lists
Use
@tanstack/react-virtualorreact-windowfor any list > 100 items.
CLS — don't make things jump
CLS is the cheapest to fix. Common causes:
| Cause | Fix |
Images without width/height | Always set them (or use aspect-ratio CSS) |
| Web fonts swapping in (FOUT/FOIT) | font-display: swap + size-adjust fallback fonts |
| Dynamically injected ads/embeds | Reserve fixed space with min-height |
| Late-loading hero text | Render server-side, not client-side |
| Animating layout properties | Animate transform instead |
Reserve aspect ratio:
.video-embed { aspect-ratio: 16 / 9; }
.image-wrapper { aspect-ratio: var(--ratio); }Or use size-adjust to match fallback font metrics to web font:
@font-face {
font-family: 'Inter Fallback';
src: local('Arial');
size-adjust: 107%;
ascent-override: 90%;
/* tools: github.com/seek-oss/capsize */
}Bundle size — what to ship, what to defer
See what's in your bundle
- Next.js:
ANALYZE=true next build(with@next/bundle-analyzer) - Vite:
vite-plugin-visualizer - General:
source-map-explorer
Common bundle bloat causes
| Cause | Fix |
| Importing the whole lodash | import debounce from 'lodash-es/debounce' (or use native) |
| Moment.js | Use date-fns (tree-shakable) or Intl.DateTimeFormat |
| Whole icon library | Per-icon imports or use SVG inline |
| Bundling polyfills for modern browsers | Set browserslist to "modern" |
| Client component when server would do | Use Server Components (Next App Router) |
| Importing dev-only code | Check that process.env.NODE_ENV branches are dead-code-eliminated |
Code splitting
// Route-level (Next App Router does this automatically)
// Per-page is already split
// Component-level lazy load
import dynamic from 'next/dynamic';
const HeavyChart = dynamic(() => import('./HeavyChart'), {
loading: () => <ChartSkeleton />,
ssr: false, // if it can't SSR
});
// Plain React
const HeavyChart = lazy(() => import('./HeavyChart'));
<Suspense fallback={<ChartSkeleton />}>
<HeavyChart />
</Suspense>Good candidates for code splitting:
- Modals / drawers (only loaded when opened)
- Below-the-fold sections (use IntersectionObserver to trigger)
- Heavy editors / charts / maps
- Localized content not on the current path
React-specific perf (only after the above)
When to memoize
useMemo — when:
- The computed value is genuinely expensive (filtering 1000+ items, parsing, formatting)
- The value is a referenced dependency of another hook that re-runs unnecessarily
- The value is passed as a prop to a
React.memo'd child
useCallback — when:
- The function is a referenced dependency in
useEffect/useMemo - The function is passed to a
React.memo'd child - The function is in a deeply re-rendering tree
React.memo — when:
- The component is rendered many times in a list
- The component is expensive AND props are stable
- You've profiled and confirmed re-renders are the bottleneck
When NOT to memoize:
- Cheap components (a button, a small div)
- Components rendered once
- Props are inline objects/arrays anyway (
{ x: 1 }is new every render → memo useless)
// Often-wrong premature memo
const Greeting = React.memo(({ name }: { name: string }) => <p>Hello {name}</p>);
// Cost: equality check on every render. Benefit: zero, since <p> is cheap.React 19+ note
React Compiler (when enabled) handles most memoization automatically. If you're on React 19 + Compiler, you can stop manually adding useMemo/useCallback and just let the compiler. Mention this in a walkthrough if relevant.
Common React perf footguns
// Bad — new object every render, defeats child memo
<Child config={{ foo: 1 }} />
// Bad — inline component, remounts every render
function Parent() {
function Inner() { return <div />; }
return <Inner />;
}
// Bad — useState init runs every render
const [data] = useState(expensiveCompute());
// Good — lazy init
const [data] = useState(() => expensiveCompute());
// Bad — useEffect with object dep, runs every render
useEffect(() => {…}, [{ id: userId }]);
// Good — primitive dep
useEffect(() => {…}, [userId]);Image optimization checklist
widthandheightattributes set (oraspect-ratioCSS)loading="lazy"for below-the-foldloading="eager"+fetchpriority="high"for the LCP image- Modern format (AVIF / WebP) with JPEG/PNG fallback
- Responsive
srcset+sizes decoding="async"(Next/Image does this by default)alttext (a11y, not perf, but never miss)
Font optimization checklist
- Self-hosted WOFF2 (avoid third-party CSS files)
font-display: swap- Subset to needed character ranges
- Variable font if you use 3+ weights
<link rel="preload">for the critical weight- Fallback font with
size-adjustto match metrics
Social + brand assets
A separate problem from photos and fonts — these ship in the document head, get crawled by social platforms, and need their own sizing/format rules.
The five files every production site needs
| File | Size | Format | Where it goes |
favicon.svg | < 2 KB | SVG | <link rel="icon" type="image/svg+xml" href="/favicon.svg"> |
favicon.ico (legacy fallback) | < 15 KB | ICO with 16×16 + 32×32 | <link rel="icon" sizes="32x32" href="/favicon.ico"> — only if you must support pre-Chromium Edge or older Safari |
apple-touch-icon.png | < 10 KB | PNG, 180×180 | <link rel="apple-touch-icon" href="/apple-touch-icon.png"> (iOS rounds the corners itself) |
og.png | < 500 KB (target ~200 KB) | PNG or JPG, 1200×630 | <meta property="og:image" content="…"> + Twitter Card |
manifest.webmanifest (optional PWA) | < 1 KB | JSON | <link rel="manifest" href="/manifest.webmanifest"> |
Favicon — SVG primary, PNG fallback
SVG favicons render crisp at any DPI, scale to OS dark mode, and are tiny. Use SVG as primary and only add a PNG/ICO fallback if your audience includes browsers that don't support SVG icons (basically: pre-2019 Edge).
Don't bother with the historical 47-link-tag favicon dance unless you're shipping to a true legacy audience. Modern minimum:
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />For a dark-aware SVG favicon:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="14" fill="light-dark(#aa3bff, #c084fc)" />
<path d="…" fill="light-dark(#fff, #0c0a14)" />
</svg>light-dark() is supported on all modern browsers and respects color-scheme: light dark.
OG / Twitter card — the 1200×630 image
Standard dimensions across platforms (Facebook, LinkedIn, Twitter/X, Slack unfurl, iMessage rich link):
- 1200 × 630 (1.91:1 aspect)
- PNG or JPG, max ~5 MB (platform caps), target ~200–500 KB
- Critical text in the center 1200×600, since some platforms crop the edges
- No fine text — render at 1× and you're done; mobile previews are ~300px wide, so anything smaller than 24px on a 1200×630 canvas is illegible
Generating OG cards via screenshot — system fonts only
A common pattern is rendering an HTML template at 1200×630 and screenshotting it. The trap: web fonts haven't loaded yet when the screenshot fires, so you get FOIT/FOUT artifacts (missing characters, fallback fonts) baked into your card.
Two ways out:
- Use system fonts in the OG template only.
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, system-ui, sans-serif. Looks fine in a card, dodges the font-loading race entirely. await document.fonts.readybefore screenshotting if you must use a custom font.
Don't try to inline a @font-face declaration in the OG template — the screenshotter's headless browser still needs to download the font file before rendering.
The meta tag set (paste-ready)
<!-- Open Graph — https://ogp.me -->
<meta property="og:type" content="website" />
<meta property="og:site_name" content="Your Site" />
<meta property="og:title" content="Your Title" />
<meta property="og:description" content="One sentence." />
<meta property="og:url" content="https://example.com/" />
<meta property="og:image" content="https://example.com/og.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:alt" content="Describe the card visually." />
<!-- Twitter Cards — https://developer.x.com/en/docs/x-for-websites/cards/ -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Your Title" />
<meta name="twitter:description" content="One sentence." />
<meta name="twitter:image" content="https://example.com/og.png" />
<meta name="twitter:image:alt" content="Describe the card visually." />
<!-- Theme + color scheme -->
<meta name="theme-color" content="#aa3bff" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#16171d" media="(prefers-color-scheme: dark)" />
<meta name="color-scheme" content="light dark" />
<!-- Canonical -->
<link rel="canonical" href="https://example.com/" />Both og: and twitter: blocks are needed — Twitter prefers its own tags but falls back to OG; LinkedIn reads only OG; Slack reads both. Ship both.
Brand asset checklist
favicon.svgexists, references brand color, dark-mode awareapple-touch-icon.png180×180, brand background, no rounded corners (iOS rounds them)og.png1200×630, < 500 KB, key content centered- Open Graph tags present (
og:title,og:description,og:url,og:image+ width/height/alt) - Twitter Card tags present (
twitter:card="summary_large_image", title/description/image/alt) theme-colormeta with light/dark variantscanonicallink- Tested in https://www.opengraph.xyz/ or https://cards-dev.twitter.com/validator (or equivalent)
Performance audit walkthrough
- Lighthouse in Chrome DevTools → mobile, simulated 4G, "Performance" category
- WebPageTest for real-world conditions across geographies
- Chrome DevTools Performance panel for INP / main-thread profiling
- Coverage panel for unused JS/CSS
- Network panel with throttling for image/font loading order
- Real User Monitoring in production: Vercel Speed Insights, Cloudflare Web Analytics, or Netlify's own RUM
When the interviewer asks "how would you make this faster?"
Senior answer follows the order from this skill:
- "First I'd check the LCP element — usually an image. Make sure it's sized, in a modern format, and not lazy-loaded."
- "Then fonts —
font-display: swap, self-hosted, preload the critical weight." - "Then layout shift — reserve aspect ratios for media."
- "Then bundle — what's importing the whole lodash? What can be a Server Component?"
- "Then React-level — but I'd profile before assuming
React.memomatters." - "I'd measure on a throttled mobile profile, not my MacBook on wifi."
Narration phrases
- "The LCP element is the hero image, so I gave it
priorityand a modern format." - "I'm not adding
useMemohere — the children are cheap, and the wrap cost would exceed the recompute cost." - "I deferred the chart with
dynamic()because it's below the fold and weighs 60 KB." - "I virtualized the list because we render 500 rows and only ~10 are visible."
- "I wrapped the search filter in
useDeferredValueso keystrokes stay responsive even on slow phones." - "The animation is on
transform, nottop/width, so it doesn't trigger layout — keeps CLS at zero."
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.

