| name | react-performance |
| license | MIT |
| description | Comprehensive React and Next.js performance optimization guide. Covers waterfall elimination, bundle size reduction, server-side optimization, re-render prevention, and rendering performance. Use when building, reviewing, or optimizing React/Next.js applications for speed. |
React Performance
Systematic performance optimization for React and Next.js applications, organized by impact.
Priority 1: Eliminate Waterfalls (CRITICAL)
Sequential async operations are the single biggest performance killer. Fix these first.
Defer Await
Move await to the point of use, not the point of declaration.
async function Page() {
const user = await getUser();
const posts = await getPosts(user.id);
return <Feed user={user} posts={posts} />;
}
async function Page() {
const userPromise = getUser();
const postsPromise = getPosts();
const [user, posts] = await Promise.all([userPromise, postsPromise]);
return <Feed user={user} posts={posts} />;
}
Suspense Streaming
Wrap slow data behind <Suspense> so the shell renders instantly.
export default function Page() {
return (
<main>
<Header /> {/* instant */}
<Suspense fallback={<Skeleton />}>
<SlowDataSection /> {/* streams in */}
</Suspense>
</main>
);
}
Partial Dependencies
When promises have partial dependencies, start independent work immediately.
async function Dashboard() {
const userPromise = getUser();
const settingsPromise = getSettings();
const user = await userPromise;
const postsPromise = getPosts(user.id);
const [settings, posts] = await Promise.all([settingsPromise, postsPromise]);
return <View user={user} settings={settings} posts={posts} />;
}
Priority 2: Bundle Size (CRITICAL)
Direct Imports
Never import from barrel files in production code.
import { Button } from '@/components';
import { format } from 'date-fns';
import { Button } from '@/components/ui/button';
import { format } from 'date-fns/format';
Dynamic Imports
Code-split heavy components that aren't needed on initial render.
import dynamic from 'next/dynamic';
const Chart = dynamic(() => import('@/components/chart'), {
loading: () => <ChartSkeleton />,
ssr: false,
});
const Editor = dynamic(() => import('@/components/editor'), {
loading: () => <EditorSkeleton />,
});
Defer Third-Party Scripts
Load analytics, logging, and non-critical scripts after hydration.
import { analytics } from 'heavy-analytics';
analytics.init();
useEffect(() => {
import('heavy-analytics').then(({ analytics }) => analytics.init());
}, []);
Preload on Intent
Preload resources when user shows intent (hover, focus).
function NavLink({ href, children }: { href: string; children: React.ReactNode }) {
const router = useRouter();
return (
<Link
href={href}
onMouseEnter={() => router.prefetch(href)}
onFocus={() => router.prefetch(href)}
>
{children}
</Link>
);
}
Priority 3: Server-Side Performance (HIGH)
Request-Scoped Deduplication
Use React.cache() to deduplicate data fetches within a single request.
import { cache } from 'react';
export const getUser = cache(async (id: string) => {
return db.user.findUnique({ where: { id } });
});
Minimize Serialization
Only pass the data client components actually need.
<ClientAvatar user={user} />
<ClientAvatar name={user.name} avatarUrl={user.avatarUrl} />
Non-Blocking Background Work
Use after() for work that shouldn't block the response.
import { after } from 'next/server';
export async function POST(request: Request) {
const data = await processRequest(request);
after(async () => {
await logAnalytics(data);
await sendNotification(data);
});
return Response.json(data);
}
Priority 4: Re-render Prevention (MEDIUM)
Derived State
Compute derived values during render, never in effects.
const [items, setItems] = useState([]);
const [count, setCount] = useState(0);
useEffect(() => setCount(items.length), [items]);
const [items, setItems] = useState([]);
const count = items.length;
Stable References
Use callback-based setState and extract callbacks outside render.
<Button onClick={() => setCount(count + 1)} />
<Button onClick={() => setCount(c => c + 1)} />
Primitive Dependencies
Use primitives in dependency arrays to avoid false positives.
useEffect(() => { ... }, [config]);
useEffect(() => { ... }, [config.apiUrl, config.timeout]);
Lazy State Initialization
Pass initializer functions for expensive initial state.
const [data, setData] = useState(expensiveParse(raw));
const [data, setData] = useState(() => expensiveParse(raw));
Memoize Expensive Components
Extract heavy computation into memoized child components.
const ExpensiveList = memo(function ExpensiveList({ items }: { items: Item[] }) {
return items.map(item => <ComplexItem key={item.id} item={item} />);
});
useTransition for Deferrable Updates
Use startTransition for non-urgent state updates.
const [isPending, startTransition] = useTransition();
function handleSearch(query: string) {
setInputValue(query);
startTransition(() => setResults(search(query)));
}
Priority 5: Rendering Performance (LOW-MEDIUM)
Content Visibility
Apply CSS content-visibility for long scrollable lists.
.list-item {
content-visibility: auto;
contain-intrinsic-size: 0 80px;
}
Hoist Static JSX
Extract JSX that doesn't depend on props/state outside the component.
function Layout({ children }) {
return (
<div>
<footer><p>Copyright 2026</p></footer>
{children}
</div>
);
}
const footer = <footer><p>Copyright 2026</p></footer>;
function Layout({ children }) {
return <div>{footer}{children}</div>;
}
Conditional Rendering
Prefer ternary over && to avoid rendering 0 or "".
{count && <Badge count={count} />}
{count > 0 ? <Badge count={count} /> : null}
Quick Reference
| Issue | Fix | Priority |
|---|
| Sequential fetches | Promise.all() / Suspense | CRITICAL |
| Barrel imports | Direct path imports | CRITICAL |
| Large initial bundle | next/dynamic | CRITICAL |
| Redundant DB calls | React.cache() | HIGH |
| Over-serialized props | Pass primitives only | HIGH |
| Derived state in useEffect | Compute during render | MEDIUM |
| Unstable callbacks | useCallback / callback setState | MEDIUM |
| Long lists | Virtualization + content-visibility | MEDIUM |
| Non-urgent updates | useTransition | MEDIUM |