به مناسبت روز برنامه‌نویس، یه فرصت ناب داری که نباید از دست بدی! 🔥

فرصت محدود، تعداد محدود
ثانیه
دقیقه
ساعت
روز
آموزش کامل lazy loading در React برای بهبود سرعت سایت
ﺯﻣﺎﻥ ﻣﻄﺎﻟﻌﻪ: 11 دقیقه

آموزش کامل lazy loading در React برای بهبود سرعت سایت

تقریباً همه‌چیز از همین‌جا شروع می‌شود: صفحه‌ی شما باز می‌شود، اما قبل از آن‌که کاربر چیزی ببیند، مرورگر باید حجم زیادی جاوااسکریپت را دانلود و اجرا کند. هر کیلوبایت اضافه یعنی چند صدم ثانیه تأخیر بیشتر؛ و این چند صدم ثانیه‌ها، جمع می‌شوند و مستقیم روی Core Web Vitals شما می‌نشینند. lazy loading در React راهی است برای قطع این زنجیره‌ی تأخیر: به‌جای اینکه همه‌چیز را همین حالا بار کنیم، فقط همان چیزی را که الان لازم است می‌آوریم.

در عمل، بسیاری از اپ‌های React ما با «یک باندل بزرگ» شروع می‌شوند: کامپوننت‌هایی که شاید فقط در صفحات خاصی استفاده شوند، لایبرری‌هایی که فقط گاهی لازم‌اند، تصاویر بزرگی که فعلاً در viewport نیستند، و مسیرهایی (routes) که کاربر ممکن است هیچ‌وقت به آن‌ها نرود. lazy loading در React قرار نیست معجزه کند، اما به شما اجازه می‌دهد این هزینه‌ها را «به‌تعویق بیندازید» و دقیقاً همان نقطه‌ای خرج کنید که به تجربه‌ی کاربر کمک می‌کند: بارگذاری اولیه سریع‌تر، تعامل زودتر، و حس بهتری از سرعت.

این تکنیک چند لایه دارد. در لایه‌ی کامپوننت، با React.lazy و React.Suspense می‌توانیم تکه‌های UI را جدا کنیم تا فقط وقتی لازم شدند دانلود شوند. در لایه‌ی مسیربندی (routing)، می‌شود هر صفحه را به‌صورت تنبل بار کرد تا ورود کاربر به صفحه‌ی اصلی سبک بماند. در بخش تصاویر، با lazy loading تصاویر و ویدیوها، زمان رندر اولیه سبک‌تر می‌شود و LCP بهتر می‌ایستد. و در لایه‌ی ابزارها، قابلیت‌های Webpack code splitting و Vite code splitting کمک می‌کنند بسته‌های جداگانه بسازیم که واقعاً فقط هنگام نیاز کشیده شوند.

اما lazy loading فقط «تقسیم کد» نیست؛ یک تصمیم تجربه‌ی کاربری هم هست. اگر جای اشتباهی تنبلی کنید، ممکن است با waterfall درخواست‌ها، پرش‌های بصری (jank)، یا fallbackهای نامناسب تجربه را خراب کنید. اگر جای درست تنبلی کنید و همزمان از پیش‌بارگذاری هوشمندانه (preload/prefetch)، skeletonهای سبک و مدیریت خطا استفاده کنید، نتیجه شفاف است: بهبود محسوس زمان بارگذاری اولیه و امتیازهای بهتر در شاخص‌های عملکردی.

lazy loading چیست و چه تفاوتی با code splitting دارد؟

lazy loading در معنای ساده‌اش یعنی «چیزی را تا زمانی که لازم نیست، بارگذاری نکن». این مفهوم در وب مدرن به‌خصوص مهم شده، چون اپلیکیشن‌های تک‌صفحه‌ای (SPA) معمولاً همه‌چیز را یکجا تحویل مرورگر می‌دهند: کامپوننت‌ها، مسیرها، تصاویر، کتابخانه‌ها. نتیجه؟ یک باندل سنگین که زمان بارگذاری اولیه (initial load time) را بالا می‌برد.

code splitting یا «تقسیم کد» در واقع ابزار این ایده است. به‌جای اینکه تمام کدهای جاوااسکریپت در یک فایل واحد باشد، آن‌ها را به چند بخش (chunk) تقسیم می‌کنیم. هر بخش فقط زمانی دانلود می‌شود که کاربر به آن نیاز دارد. در ری‌اکت، lazy loading همان استفاده‌ی عملی از code splitting است، یعنی انتخاب دقیق اینکه کدام تکه‌ها را عقب بیندازیم.

اما چرا این تفاوت مهم است؟

  • code splitting یک تکنیک است: تقسیم فایل‌ها با ابزارهایی مثل Webpack یا Vite.
  • lazy loading یک استراتژی است: تصمیم می‌گیرد چه زمانی این بخش‌های تقسیم‌شده را دانلود کنیم.

مثال ساده:

فرض کنید وب‌سایتی دارید با بخش بلاگ، فروشگاه و داشبورد کاربری. اگر همه این بخش‌ها در باندل اصلی باشند، کاربر حتی برای دیدن صفحه‌ی اصلی فروشگاه باید کدهای مربوط به داشبورد را هم دانلود کند. اما با lazy loading، کامپوننت‌های مربوط به داشبورد فقط زمانی بارگذاری می‌شوند که کاربر واقعاً وارد آن بخش شود.

در ادامه، خواهیم دید که React.lazy و React.Suspense چگونه این ایده را برای کامپوننت‌ها پیاده می‌کنند، و بعد به سراغ بارگذاری تنبل مسیرها و حتی تصاویر می‌رویم.

React.lazy و React.Suspense؛ نقطه‌ی ورود به lazy loading در React

برای اینکه بتوانیم در React به‌صورت واقعی lazy loading داشته باشیم، دو ابزار اصلی در اختیار داریم:

  • React.lazy
  • React.Suspense

این دو ویژگی که از نسخه‌ی 16.6 به بعد اضافه شدند، باعث شدند بتوانیم هر کامپوننتی را تنها زمانی بارگذاری کنیم که واقعاً نیاز داریم.

React.lazy چیست؟

React.lazy به شما اجازه می‌دهد یک کامپوننت را به‌صورت پویا ایمپورت کنید. یعنی به جای اینکه از همان ابتدای کار تمام کدهای کامپوننت در باندل اصلی بیاید، فقط زمانی که رندر می‌شود، از روی سرور دانلود و اجرا شود. شکل ساده‌ی استفاده:

import React, { Suspense } from "react";

const HeavyComponent = React.lazy(() => import("./HeavyComponent"));

function App() {
  return (
    <div>
      <h1>صفحه اصلی</h1>
      <Suspense fallback={<div>در حال بارگذاری...</div>}>
        <HeavyComponent />
      </Suspense>
    </div>
  );
}

export default App;

در این مثال:

  • HeavyComponent تا زمانی که نیاز نباشد بارگذاری نمی‌شود.
  • Suspense یک fallback UI نمایش می‌دهد تا کاربر منتظر دانلود و رندر شدن کامپوننت بماند.

Suspense چه می‌کند؟

React.Suspense یک پوشش (wrapper) است که به React می‌گوید: «ممکن است داخل این محدوده، بخشی از UI دیرتر آماده شود. تا وقتی آماده شود، این چیزی را که در fallback تعیین شده نشان بده.»
این همان جایی است که تجربه‌ی کاربری خوب یا بد ساخته می‌شود. اگر یک Skeleton ساده یا spinner سبک داشته باشید، کاربر حس نمی‌کند چیزی گیر کرده است.

نکات مهم در استفاده از React.lazy و Suspense

  • بهتر است هر کامپوننت سنگینی که فقط در شرایط خاص استفاده می‌شود، lazy شود (مثل modalها، فرم‌های خاص، گراف‌های سنگین).
  • همیشه یک fallback مناسب نمایش دهید. از لودینگ‌های حجیم یا گیف‌های بزرگ استفاده نکنید.
  • lazy loading فقط برای کامپوننت‌های default export کار می‌کند؛ اگر چندین export دارید، باید یک wrapper بنویسید یا ساختار کدتان را تغییر دهید.
  • مراقب باشید که تعداد زیاد chunkهای کوچک باعث درخواست‌های پشت‌سرهم (waterfall) نشود.

بارگذاری تنبل مسیرها (Lazy Loading Routes) در React

تا اینجا lazy loading را روی سطح کامپوننت‌ها دیدیم، اما اگر پروژه‌ی شما چندین صفحه و مسیر (route) دارد، بهتر است بارگذاری تنبل را روی سطح مسیرها هم پیاده کنید. چرا؟ چون معمولاً کاربران وارد همه‌ی صفحات یک سایت یا اپلیکیشن نمی‌شوند. پس منطقی است بخش‌های غیرضروری در باندل اولیه نباشند.

چطور در React Router از lazy loading استفاده کنیم؟

از نسخه‌ی 6 به بعد، React Router پشتیبانی خوبی از lazy loading ارائه می‌دهد. مراحل کلی این است:

  1. هر صفحه را به‌صورت یک کامپوننت lazy تعریف کنید.
  2. در تنظیمات مسیرها از این کامپوننت‌ها استفاده کنید.
  3. یک Suspense کلی قرار دهید تا هنگام بارگذاری هر صفحه، UI جایگزین نمایش داده شود.

نمونه‌ی کد:

import { BrowserRouter, Routes, Route } from "react-router-dom";
import React, { Suspense } from "react";

const HomePage = React.lazy(() => import("./pages/HomePage"));
const AboutPage = React.lazy(() => import("./pages/AboutPage"));
const Dashboard = React.lazy(() => import("./pages/Dashboard"));

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div>در حال بارگذاری صفحه...</div>}>
        <Routes>
          <Route path="/" element={<HomePage />} />
          <Route path="/about" element={<AboutPage />} />
          <Route path="/dashboard" element={<Dashboard />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

export default App;

در این کد:

  • هر صفحه فقط زمانی دانلود می‌شود که کاربر به مسیر مربوطه برود.
  • Suspense از پرش‌های ناگهانی جلوگیری می‌کند و تجربه کاربری روان‌تر می‌سازد.

چه نکاتی را رعایت کنیم؟

  • مسیرهایی که ترافیک بالایی دارند یا همیشه نیاز هستند، بهتر است prefetch شوند. (مثلاً صفحه‌ی اصلی یا بخش ورود)
  • برای صفحات سنگین، Skeleton UI طراحی کنید تا لودینگ طبیعی‌تر به نظر برسد.
  • اگر در پروژه‌تان از layout مشترک استفاده می‌کنید، layout اصلی را در باندل اولیه نگه دارید و فقط محتوای صفحات را lazy کنید.

بارگذاری تنبل تصاویر و رسانه‌ها در React

تصاویر معمولاً یکی از سنگین‌ترین بخش‌های هر صفحه وب هستند. حتی اگر اپلیکیشن شما جاوااسکریپت سبک و بهینه‌ای داشته باشد، چند تصویر بزرگ می‌تواند زمان بارگذاری اولیه را به‌شدت بالا ببرد و شاخص‌های Core Web Vitals مثل LCP (Largest Contentful Paint) را خراب کند. راه‌حل؟ بارگذاری تنبل تصاویر یا Lazy Loading Images.

بارگذاری تنبل تصاویر یعنی چه؟

یعنی تصاویر فقط زمانی دانلود شوند که کاربر واقعاً آن‌ها را در صفحه ببیند (یا در آستانه‌ی دیدنشان باشد). به این ترتیب، مرورگر نیازی ندارد همه‌ی تصاویر را یک‌جا و از همان لحظه اول بگیرد.

چطور در React تصاویر را lazy کنیم؟

روش‌های مختلفی وجود دارد:

1. استفاده از ویژگی loading="lazy" (راه ساده و سریع)

HTML5 ویژگی ساده‌ای دارد که در مرورگرهای مدرن پشتیبانی می‌شود:

<img src="/images/large-photo.jpg" alt="نمونه" loading="lazy" />

مزیت: ساده و بدون نیاز به کتابخانه اضافی.
محدودیت: همه مرورگرهای قدیمی پشتیبانی نمی‌کنند، و گزینه‌های پیشرفته مثل placeholder ندارد.

2. استفاده از Intersection Observer (کنترل پیشرفته)

اگر بخواهید تجربه‌ی بهتری بسازید (مثلاً ابتدا یک نسخه کم‌کیفیت نشان دهید و سپس تصویر اصلی لود شود)، می‌توانید از Intersection Observer API استفاده کنید.

نمونه‌ی ساده در React:

import { useEffect, useRef, useState } from "react";

function LazyImage({ src, alt, placeholder }) {
  const imgRef = useRef(null);
  const [loaded, setLoaded] = useState(false);

  useEffect(() => {
    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting) {
            setLoaded(true);
            observer.disconnect();
          }
        });
      },
      { rootMargin: "100px" }
    );

    if (imgRef.current) {
      observer.observe(imgRef.current);
    }

    return () => observer.disconnect();
  }, []);

  return (
    <img
      ref={imgRef}
      src={loaded ? src : placeholder}
      alt={alt}
      style={{ transition: "opacity 0.3s ease" }}
    />
  );
}

در این کد:

  • وقتی تصویر در viewport ظاهر شود (یا نزدیک آن باشد)، آدرس اصلی بارگذاری می‌شود.
  • تا قبل از آن، یک placeholder سبک نمایش داده می‌شود.

3. کتابخانه‌های آماده

اگر پروژه‌ی شما بزرگ است یا نیاز به امکانات بیشتری دارید، کتابخانه‌هایی مثل react-lazyload یا react-intersection-observer کمک می‌کنند سریع‌تر این کار را انجام دهید.

نکات مهم برای lazy loading تصاویر

  • همیشه برای تصاویر ابعاد مشخص کنید تا از پرش layout جلوگیری شود.
  • برای تصاویر مهم (مانند تصویر هدر یا قهرمان صفحه)، از lazy استفاده نکنید، چون باید سریع دیده شوند.
  • می‌توانید از تکنیک blur-up یا skeleton برای تجربه‌ی بهتر استفاده کنید.

Webpack و Vite؛ تقسیم کد و lazy loading در سطح باندل

تا اینجا دیدیم چطور می‌توانیم در سطح کامپوننت، مسیر و تصاویر، بارگذاری تنبل داشته باشیم. اما این کارها پشت‌صحنه به ابزارهایی وابسته‌اند که کد شما را به باندل‌های جدا تقسیم کنند. دو ابزار رایج که این کار را ساده کرده‌اند، Webpack و Vite هستند.

تقسیم کد در وب‌پک (Webpack Code Splitting)

Webpack مدت‌هاست استانداردی برای مدیریت باندل جاوااسکریپت است. ویژگی تقسیم کد (code splitting) به شما امکان می‌دهد کدها را به چند بخش (chunk) تقسیم کنید تا در لحظه نیاز دانلود شوند. در ری‌اکت، React.lazy در واقع از همین قابلیت بهره می‌برد.

نمونه ساده:

const HeavyComponent = React.lazy(() => import("./HeavyComponent"));

در اینجا، Webpack یک chunk جدا برای HeavyComponent می‌سازد. وقتی به این کامپوننت نیاز دارید، مرورگر درخواست جداگانه‌ای برای آن chunk ارسال می‌کند.

نکات مهم برای Webpack:

  • Dynamic import: کلید اصلی تقسیم کد استفاده از import() است.
  • Chunk naming: برای مدیریت بهتر، می‌توانید اسم chunk را مشخص کنید:
const HeavyComponent = React.lazy(() => import(/* webpackChunkName: "heavy" */ "./HeavyComponent"));
  • Prefetch و Preload: وب‌پک از دستورهای خاص برای پیش‌بارگذاری (prefetch) یا بارگذاری زودهنگام (preload) پشتیبانی می‌کند. این کار تجربه کاربری را بهتر می‌کند بدون اینکه باندل اولیه سنگین شود.

تقسیم کد در Vite (Vite Code Splitting)

Vite که ابزار جدیدتر و سریع‌تری است، به‌طور پیش‌فرض از ES modules و تقسیم کد پشتیبانی می‌کند. درست مثل Webpack، می‌توان از import() استفاده کرد:

const HeavyComponent = React.lazy(() => import("./HeavyComponent"));

Vite به‌صورت پیش‌فرض هر ایمپورت پویا را به یک فایل جدا تبدیل می‌کند. مزیت Vite این است که به‌دلیل معماری متفاوت و استفاده از ESBuild، سرعت توسعه و build بسیار بالاتر است و خروجی بهینه‌تری برای lazy loading ارائه می‌دهد.

ویژگی‌های مهم Vite برای lazy loading:

حجم باندل کمتر به‌دلیل خروجی مدرن‌تر.

  • سرعت توسعه بالا: تغییرات در لحظه اعمال می‌شوند و chunks به‌سرعت ساخته می‌شوند.
  • پشتیبانی از prefetch/prefetch: مثل Webpack می‌توان از لینک‌های prefetch استفاده کرد تا منابع قبل از نیاز بارگیری شوند.

چه زمانی تقسیم کد ارزش دارد؟

  • وقتی اپلیکیشن شما بزرگ است و مسیرها و کامپوننت‌های زیادی دارد.
  • زمانی که کاربران به‌ندرت به برخی بخش‌ها سر می‌زنند.
  • زمانی که شاخص‌های Core Web Vitals مخصوصاً FID و LCP پایین هستند.

در پایان

lazy loading در React تنها یک تکنیک نیست، بلکه یک رویکرد فکری برای ساخت اپلیکیشن‌های سریع‌تر و تجربه‌ی کاربری بهتر است. ایده‌ی اصلی ساده است: چیزی را که لازم نیست همین حالا، بارگذاری نکن. اما اجرای درست آن نیاز به شناخت ابزارها و ظرافت‌های طراحی دارد.

در این مطلب دیدیم:

  • مفهوم lazy loading و ارتباط آن با code splitting؛ اینکه تقسیم کد با ابزارهایی مثل Webpack و Vite چگونه به این فرآیند قدرت می‌دهد.
  • React.lazy و React.Suspense به‌عنوان ابزار اصلی در React برای بارگذاری تنبل کامپوننت‌ها و مدیریت UI در هنگام لودینگ.
  • بارگذاری تنبل مسیرها (routes) با React Router که به سبک‌تر شدن باندل اولیه کمک می‌کند.
  • بارگذاری تنبل تصاویر با استفاده از ویژگی‌های ساده‌ی HTML یا Intersection Observer برای کنترل بهتر.
  • نکات عملی برای Webpack و Vite در تقسیم کد و مدیریت chunkها.

مزیت‌ها مشخص‌اند: زمان بارگذاری اولیه کاهش می‌یابد، شاخص‌های Core Web Vitals بهبود پیدا می‌کند، و کاربران تجربه‌ی سریع‌تر و روان‌تری دارند. اما مهم است به‌خاطر داشته باشید که lazy loading در جای اشتباه می‌تواند نتیجه‌ی معکوس بدهد: waterfall درخواست‌ها، تجربه‌ی کاربری ضعیف و حتی مشکلات SEO.

چند نکته‌ی عملی برای شروع:

  • از lazy loading برای بخش‌های سنگین و کمتر استفاده‌شده شروع کنید، نه همه‌چیز.
  • همیشه یک fallback مناسب (spinner ،skeleton، متن ساده) داشته باشید.
  • تصاویر مهم و بالای صفحه را lazy نکنید، چون باید سریع دیده شوند.
  • به پیش‌بارگذاری (prefetch/prefetch) برای مسیرها یا منابع مهم فکر کنید تا کاربر حتی متوجه تاخیر نشود.
  • شاخص‌های عملکردی (Lighthouse و Web Vitals) را بعد از هر تغییر بررسی کنید.

در نهایت، lazy loading در React مثل یک ابزار جراحی است: اگر درست استفاده شود، وزن اضافه را از روی دوش کاربر برمی‌دارد و تجربه‌ی استفاده از وب‌سایت شما را سریع‌تر و لذت‌بخش‌تر می‌کند.

چه امتیازی برای این مقاله میدهید؟

خیلی بد
بد
متوسط
خوب
عالی
در انتظار ثبت رای

/@arastoo
ارسطو عباسی
کارشناس تولید و بهینه‌سازی محتوا

کارشناس ارشد تولید و بهینه‌سازی محتوا و تکنیکال رایتینگ - https://arastoo.net

دیدگاه و پرسش

برای ارسال دیدگاه لازم است وارد شده یا ثبت‌نام کنید ورود یا ثبت‌نام

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

ارسطو عباسی

کارشناس تولید و بهینه‌سازی محتوا