--- 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