मापना हमेशा सुधारने से पहले आना चाहिए। बहुत-सी React टीमें आँख मूँदकर सुधार करती हैं — हर उस मान पर useMemo और useCallback चढ़ा देती हैं जो हाथ आ जाए, और कभी प्रोफ़ाइल लेकर देखती ही नहीं कि इससे फ़र्क़ पड़ा या नहीं। दुर्भाग्य से, अंगूठे के नियम पर चलने वाला यह सुधार लगभग हमेशा कोड को तेज़ नहीं, धीमा करता है, क्योंकि मेमोइज़ेशन की अपनी क़ीमत है: वह हर रेंडर पर निर्भरताओं की तुलना करता है और स्मृति घेरता है।
यह मार्गदर्शिका 2026 में React के प्रदर्शन को सुधारने के सिद्धांतों और औज़ारों पर है। हम देखेंगे कि React डिफ़ॉल्ट रूप से किस वजह से तेज़ है, अड़चनें असल में कहाँ बनती हैं, और कोड को उलझाए बिना कैसे मापा और सुधारा जाए। मुख्य ज़ोर सर्वर कंपोनेंट पर, useMemo और useCallback के पुनर्मूल्यांकन पर, और बिल्ड-समय के सुधारों के बढ़ते महत्व पर है।
मापन: एकमात्र चीज़ जो गिनी जाती है
React DevTools अब भी पहला पड़ाव है। प्रोफ़ाइलर टैब हर रेंडर का कमिट समय दिखाता है और ठीक-ठीक बताता है कि कौन-से कंपोनेंट सबसे महँगे हैं। सबसे उपयोगी है फ़्लेम दृश्य: कमिट की अवधि रंगों में दिखती है, इसलिए अपवाद तुरंत नज़र आ जाते हैं। अगर कोई अपवाद दिख ही नहीं रहा, तो सुधार करने से कोड सिर्फ़ बिगड़ेगा।
// Measuring with the Profiler API
import { Profiler } from "react";
function onRenderCallback(
id: string,
phase: "mount" | "update",
actualDuration: number,
baseDuration: number,
startTime: number,
commitTime: number
) {
if (actualDuration > 16) {
console.warn(`Slow render: ${id} took ${actualDuration.toFixed(2)}ms`);
}
}
function App() {
return (
<Profiler id="App" onRender={onRenderCallback}>
<Dashboard />
</Profiler>
);
}16 मिलीसेकंड की सीमा 60 फ़्रेम प्रति सेकंड से मेल खाती है — इससे लंबा कोई भी कमिट स्क्रीन पर दिखने वाली अटकन पैदा करता है। प्रोफ़ाइलर API केवल विकास के लिए नहीं है; सशर्त लॉगिंग के साथ इसे प्रोडक्शन में छोड़ना सुरक्षित है और इससे असली प्रदर्शन का पता चलता रहता है। सबसे सस्ता सुधार यही है कि मापन जोड़ दें — तब आप अनुमान लगाना बंद करते हैं और जानने लगते हैं।
- प्रोडक्शन में मापें। विकास में सब तेज़ लगता है, और लैपटॉप का हार्डवेयर उन समस्याओं को छिपा देता है जो असली मोबाइल उपकरणों पर सामने आती हैं।
- बड़ी चीज़ों पर ध्यान दें। एक बार रेंडर होने वाले 200 मि.से. के कंपोनेंट को सुधारना व्यर्थ है; 200 बार रेंडर होने वाले 20 मि.से. के कंपोनेंट को सुधारना सोने के बराबर है।
- रुझान देखें, अकेले आँकड़े नहीं। एक अकेला माप शोर है; कई पृष्ठ-दृश्यों का औसत संकेत है।
React सर्वर कंपोनेंट — सबसे बड़ा लाभ
React सर्वर कंपोनेंट उन कंपोनेंटों को सर्वर पर ले जाकर खेल ही बदल देते हैं जिन्हें अन्तःक्रिया की ज़रूरत नहीं। सर्वर कंपोनेंट सर्वर पर ठीक एक बार चलता है, स्थिर HTML बनाता है और क्लाइंट को कोई JavaScript नहीं भेजता। यह सुधार नहीं है — यह काम की एक पूरी श्रेणी का उन्मूलन है।
// Server Component - no client JS, no hydration
// In Next.js App Router, files default to server components
async function ProductList() {
const products = await db.products.findAll(); // direct DB access
return (
<div className="grid grid-cols-3 gap-4">
{products.map((product) => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}
// Only this interactive part ships to the client
"use client";
function AddToCart({ productId }: { productId: string }) {
const [added, setAdded] = useState(false);
return (
<button onClick={() => {
addToCart(productId);
setAdded(true);
}}>
{added ? "In cart" : "Add to cart"}
</button>
);
}ऊपर के उदाहरण में ProductList ब्राउज़र को एक बाइट JavaScript नहीं भेजता। वह सर्वर पर रेंडर होता है और क्लाइंट को केवल तैयार HTML मिलता है। यह कोई नई तरकीब नहीं, बल्कि मूलतः अलग वास्तुकला है जिसमें सर्वर का तर्क क्लाइंट के संसाधन ख़र्च नहीं करता। डेटाबेस तक पहुँच सीधी होती है, बिना किसी API रूट के, जिससे एक नेटवर्क चक्कर बच जाता है। बंडल छोटा होता है क्योंकि सर्वर कोड उसमें जाता ही नहीं। पहला पृष्ठ-लोड तेज़ होता है क्योंकि कम JavaScript डाउनलोड, पार्स और निष्पादित करना पड़ता है।
नियम सरल है: डिफ़ॉल्ट सर्वर है। किसी कंपोनेंट को केवल तभी 'use client' से चिह्नित करें जब उसे अन्तःक्रिया चाहिए — useState, useEffect, onClick, ब्राउज़र API। बाक़ी सब सर्वर कंपोनेंट ही रहे। अकेले इसी से अधिकांश ऐप्लिकेशन में क्लाइंट बंडल 40–60% घट जाता है।
useMemo और useCallback — संयम और निशाना
useMemo और useCallback React के सबसे अधिक दुरुपयोग किए जाने वाले API हैं। 2026 की डिफ़ॉल्ट सलाह यह है: जब तक प्रोफ़ाइलर न कहे कि ज़रूरत है, इनका उपयोग न करें। कारण यह है कि मेमोइज़ेशन मुफ़्त नहीं है। हर useMemo कॉल हर रेंडर पर निर्भरताओं की तुलना कराती है। अगर यह तुलना मान को दोबारा गणना करने से सस्ती है, आप समय बचाते हैं। अगर नहीं, तो गँवाते हैं।
// Before - premature memoization
const sortedItems = useMemo(
() => items.sort((a, b) => a.name.localeCompare(b.name)),
[items]
);
// This useMemo is either:
// 1. Wasted - if items is a new reference every render, the sort runs anyway
// 2. Correct - if items reference is stable AND sorting 10k+ items
// Profile first, then decide
// After - only memoize when the profiler shows a problem
const sortedItems = items.sort((a, b) => a.name.localeCompare(b.name));
// Correct usage - child uses React.memo
const HeavyList = React.memo(function HeavyList({ items }: { items: Item[] }) {
return items.map((item) => <HeavyRow key={item.id} item={item} />);
});
// Now useCallback matters - it keeps the reference stable
const handleClick = useCallback((id: string) => {
selectItem(id);
}, []);
return <HeavyList items={items} onSelect={handleClick} />;अंगूठे का नियम यह है: अगर प्रोफ़ाइलर कोई महँगा रेंडर नहीं दिखाता, तो useMemo कुछ नहीं जोड़ता। अगर रेंडर सचमुच महँगा है, पहले देखें कि कंपोनेंट को दोबारा रेंडर होने की ज़रूरत है भी या नहीं — अक्सर सही उत्तर यह होता है कि उसे React.memo में लपेट दें या सर्वर कंपोनेंट बना दें। useCallback भी उन्हीं नियमों पर चलता है: वह तभी अर्थपूर्ण है जब आप फ़ंक्शन उस बच्चे को दे रहे हों जो React.memo का उपयोग करता है। वरना स्थिर संदर्भ का कोई अर्थ नहीं, क्योंकि बच्चा हर बार वैसे भी दोबारा रेंडर होगा।
अपवाद हैं। अगर कोई useEffect किसी फ़ंक्शन या ऑब्जेक्ट पर निर्भर है, संदर्भ को स्थिर रखने से अनंत पुनर्निष्पादन रुकता है। अगर आप दसियों हज़ार प्रविष्टियों पर कोई मान निकाल रहे हैं, मेमोइज़ेशन से ठोस फ़र्क़ पड़ता है। पर ये अपवाद ही हैं, नियम नहीं। डिफ़ॉल्ट यही रहे कि चीज़ें सरल रखें और मापें।
useTransition — बिना रुकावट के प्रतिक्रियाशीलता
useTransition प्रदर्शन का सबसे महत्वपूर्ण API है जिसका अधिकांश डेवलपर उपयोग नहीं करते। यह आपको स्टेट अपडेट को «ट्रांज़िशन» के रूप में चिह्नित करने देता है — ऐसे अपडेट जिन्हें अधिक प्राथमिकता वाले काम से बाधित किया जा सके। React प्रतिक्रियाशील अपडेट (टाइपिंग, क्लिक) को ट्रांज़िशन पर वरीयता देता है। नतीजा यह कि इनपुट फ़ुर्तीला बना रहता है, भले ही खोज-परिणाम आने में समय लगे।
function SearchPage() {
const [query, setQuery] = useState("");
const [isPending, startTransition] = useTransition();
const results = useSearch(query);
return (
<div>
<input
value={query}
onChange={(e) => {
// Urgent: update the input value immediately
// Transition: search results can be deferred
startTransition(() => {
setQuery(e.target.value);
});
}}
/>
{isPending ? (
<Spinner />
) : (
<SearchResults results={results} />
)}
</div>
);
}useTransition के बिना हर कुंजी-दबाव, जो परिणामों का पुनर्रेंडर छेड़ता है, रेंडर पूरा होने तक थ्रेड रोक देता है। उपयोगकर्ता को लगता है कि कीबोर्ड अटक रहा है। useTransition के साथ आप React को बताते हैं कि इनपुट का मान तुरंत बदलना ज़रूरी है, जबकि परिणामों का रेंडर रुक सकता है। ज़रूरत पड़ने पर React ट्रांज़िशन का काम रोककर अधिक ज़रूरी अपडेट को रास्ता देता है।
useDeferredValue इसी परिवार का API है, उन स्थितियों के लिए जहाँ मान ऊपर से आता है (प्रॉप या कॉन्टेक्स्ट से) और आपके नियंत्रण में नहीं, पर आप उस पर निर्भर महँगे रेंडर को टालना चाहते हैं। यह एक मान लेकर उसकी स्थगित प्रति लौटाता है, जो अत्यावश्यक अपडेट आने पर पीछे हट जाती है।
कोड विभाजन और बंडल विश्लेषण
सर्वर कंपोनेंट के बावजूद क्लाइंट JavaScript कम नहीं होता। कोड विभाजन आपके बंडल को छोटे टुकड़ों में बाँटता है जो ज़रूरत पर लोड होते हैं। 2026 में इसका तरीक़ा react.lazy के साथ Suspense है। सर्वर कंपोनेंट की संरचना इसे और स्वाभाविक बनाती है, क्योंकि क्लाइंट कंपोनेंट को वहीं गतिशील रूप से आयात किया जा सकता है जहाँ वे उपयोग होते हैं।
import { Suspense, lazy } from "react";
const HeavyChart = lazy(() => import("./HeavyChart"));
function Dashboard() {
return (
<div>
<Header />
<Suspense fallback={<ChartSkeleton />}>
<HeavyChart /> {/* loaded only when rendered */}
</Suspense>
</div>
);
}किसी विश्लेषक से अपने बंडल का निरीक्षण करें। वह ढूँढें जो बड़ा नहीं होना चाहिए: एक अकेले सहायक फ़ंक्शन के लिए खींची गई पूरी लाइब्रेरी, एक ही लाइब्रेरी की दो प्रतियाँ, ग़लती से आयात हो गए सर्वर मॉड्यूल। अनावश्यक JavaScript का हर हटाया गया किलोबाइट हर उपयोगकर्ता के हर पृष्ठ-दृश्य पर लाभ है।
आधुनिक बंडलर (Turbopack, Vite) रूट के हिसाब से कोड अपने आप बाँट देते हैं, पर पृष्ठ के भीतर हाथ से किया गया विभाजन अब भी क़ीमती है। बड़े चार्ट, रिच-टेक्स्ट संपादक, वीडियो प्लेयर — जो भी बड़ा या कभी-कभार का हो, उसे आलसी ढंग से लोड करें। Suspense की सीमा एक स्वाभाविक लोडिंग अवस्था देती है ताकि उपयोगकर्ता ख़ाली स्क्रीन न ताके।
- अपने बंडल को नियमित रूप से विश्लेषक से देखें। आप हैरान होंगे कि उसमें क्या-क्या पहुँच जाता है।
- 20 KB (संपीड़ित) से बड़े हर कंपोनेंट को आलसी ढंग से लोड करें, बशर्ते वह हर पृष्ठ पर ज़रूरी न हो।
- सर्वर कंपोनेंट बंडल के आकार का गणित बदल देते हैं: सर्वर कंपोनेंट में किया गया गतिशील आयात क्लाइंट कंपोनेंट को तभी लाता है जब वह सचमुच उपयोग हो।
आभासी स्क्रॉलिंग और डेटा वर्चुअलाइज़ेशन
जब उपयोगकर्ता हज़ार प्रविष्टियों की सूची देख रहा हो, React को सब पहले से रेंडर करने की ज़रूरत नहीं। बस उतने ही रेंडर करें जितने दृश्य-क्षेत्र में समाते हैं और स्क्रॉल के साथ DOM नोड दोबारा काम में लें। react-window और @tanstack/react-virtual जैसी लाइब्रेरियाँ यह थोड़े प्रयास में कर देती हैं। प्रदर्शन का अंतर क्रमिक नहीं होता — यह सहज और अनुपयोगी इंटरफ़ेस के बीच का अंतर है।
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: () => 50,
});
return (
<div ref={parentRef} style={{ height: "600px", overflow: "auto" }}>
<div style={{ height: virtualizer.getTotalSize() }}>
{virtualizer.getVirtualItems().map((virtualItem) => (
<div
key={virtualItem.key}
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: virtualItem.size,
transform: `translateY(${virtualItem.start}px)`,
}}
>
<ItemRenderer item={items[virtualItem.index]} />
</div>
))}
</div>
</div>
);
}तरकीब यह है कि निरपेक्ष स्थिति-निर्धारण की जगह transform का उपयोग हो। translateY से कंटेनर खिसकाना एक कंपोज़िटिंग संक्रिया है जो लेआउट की पुनर्गणना नहीं छेड़ती, इसलिए स्क्रॉल GPU पर सहज बना रहता है। वर्चुअलाइज़ेशन लाइब्रेरी ठीक-ठीक गणना करती है कि स्क्रॉल की मौजूदा स्थिति पर कौन-सी प्रविष्टियाँ दिखनी चाहिए, और स्क्रीन में समाने वाली संख्या से अधिक कभी रेंडर नहीं करती। एक लाख प्रविष्टियों पर भी सूची DOM में केवल 10–20 नोड रखती है।
छवियों और मीडिया का अनुकूलन
अधिकांश वेबसाइटों पर पृष्ठ के वज़न में सबसे बड़ा योगदान छवियों का होता है। हर बिना अनुकूलित छवि प्रदर्शन की हानि है। 2026 में React में छवियों का अनुकूलन इसी पर टिका है कि Next.js का next/image या आपके फ़्रेमवर्क का समकक्ष कंपोनेंट उपयोग हो। ये कंपोनेंट उत्तरदायी आकार, WebP/AVIF रूपांतरण, आलसी लोडिंग और लेआउट-खिसकाव की रोकथाम अपने आप संभालते हैं।
Next.js से बाहर के प्रोजेक्ट में सुनिश्चित करें कि: (1) हर <img> टैग पर width और height हों ताकि लेआउट न खिसके, (2) पहली स्क्रीन से नीचे की छवियों पर loading='lazy' हो, (3) कई आकार बनाकर srcset का उपयोग हो, (4) प्रोडक्शन में आधुनिक प्रारूपों में रूपांतरण हो। ये चार बदलाव आमतौर पर छवि लोड होने का समय 60–80% घटा देते हैं।
निष्कर्ष
2026 में React का प्रदर्शन तरकीबों से ज़्यादा वास्तुकला का विषय है। सर्वर कंपोनेंट डिफ़ॉल्ट ही बदल देते हैं: अधिकांश काम सर्वर पर होता है, जहाँ उसे होना चाहिए। क्लाइंट पर रेंडर उतने तक सीमित रहता है जिसे सचमुच अन्तःक्रियात्मक होना है। इस वास्तुकला के भीतर useMemo और useCallback विशेष औज़ार बन जाते हैं, आम प्रथा नहीं। प्रदर्शन-सुधार का हर फ़ैसला प्रोफ़ाइलर के नतीजे से शुरू होना चाहिए। पहले मापें, फिर परिकल्पना बनाएँ, सुधारें और असर जाँचें।