---
name: react-performance
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.
```tsx
// BAD: Sequential — total time = fetch1 + fetch2
async function Page() {
const user = await getUser();
const posts = await getPosts(user.id);
return ;
}
// GOOD: Parallel where possible
async function Page() {
const userPromise = getUser();
const postsPromise = getPosts(); // if independent
const [user, posts] = await Promise.all([userPromise, postsPromise]);
return ;
}
```
### Suspense Streaming
Wrap slow data behind `` so the shell renders instantly.
```tsx
export default function Page() {
return (
{/* instant */}
}>
{/* streams in */}
);
}
```
### Partial Dependencies
When promises have partial dependencies, start independent work immediately.
```tsx
async function Dashboard() {
const userPromise = getUser();
const settingsPromise = getSettings(); // independent
const user = await userPromise;
const postsPromise = getPosts(user.id); // depends on user
const [settings, posts] = await Promise.all([settingsPromise, postsPromise]);
return ;
}
```
## Priority 2: Bundle Size (CRITICAL)
### Direct Imports
Never import from barrel files in production code.
```tsx
// BAD: Pulls entire library
import { Button } from '@/components';
import { format } from 'date-fns';
// GOOD: Tree-shakeable
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.
```tsx
import dynamic from 'next/dynamic';
const Chart = dynamic(() => import('@/components/chart'), {
loading: () => ,
ssr: false, // skip SSR for client-only components
});
const Editor = dynamic(() => import('@/components/editor'), {
loading: () => ,
});
```
### Defer Third-Party Scripts
Load analytics, logging, and non-critical scripts after hydration.
```tsx
// BAD: Blocks hydration
import { analytics } from 'heavy-analytics';
analytics.init();
// GOOD: Load after hydration
useEffect(() => {
import('heavy-analytics').then(({ analytics }) => analytics.init());
}, []);
```
### Preload on Intent
Preload resources when user shows intent (hover, focus).
```tsx
function NavLink({ href, children }: { href: string; children: React.ReactNode }) {
const router = useRouter();
return (
router.prefetch(href)}
onFocus={() => router.prefetch(href)}
>
{children}
);
}
```
## Priority 3: Server-Side Performance (HIGH)
### Request-Scoped Deduplication
Use `React.cache()` to deduplicate data fetches within a single request.
```tsx
import { cache } from 'react';
export const getUser = cache(async (id: string) => {
return db.user.findUnique({ where: { id } });
});
// Called in layout.tsx AND page.tsx — only one DB query
```
### Minimize Serialization
Only pass the data client components actually need.
```tsx
// BAD: Serializes entire user object
// GOOD: Pass only what's needed
```
### Non-Blocking Background Work
Use `after()` for work that shouldn't block the response.
```tsx
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); // returns immediately
}
```
## Priority 4: Re-render Prevention (MEDIUM)
### Derived State
Compute derived values during render, never in effects.
```tsx
// BAD: Extra render cycle
const [items, setItems] = useState([]);
const [count, setCount] = useState(0);
useEffect(() => setCount(items.length), [items]);
// GOOD: Derive during render
const [items, setItems] = useState([]);
const count = items.length;
```
### Stable References
Use callback-based setState and extract callbacks outside render.
```tsx
// BAD: New function every render