در دنیای وب، کاربرها انتظار دارن که صفحات خیلی سریع بارگذاری بشن. یکی از روشهای بهبود تجربه کاربری و کاهش زمان لود صفحات، Streaming است. از Next.js 13+ این قابلیت به صورت داخلی پشتیبانی میشه و به ما اجازه میده تا دادهها رو بهصورت تکهتکه (chunked) به مرورگر ارسال کنیم. در این مقاله، ابتدا مفهوم Streaming رو توضیح میدم، سپس نحوه پیادهسازی رو در Next.js یاد میگیریم.

استریمینگ از ایدهای به وجود اومد که اصلاً قصد داشت به ما اجازه بده بدون اینکه کل فایل یا داده رو دانلود کنیم، بتونیم ازش لذت ببریم.وقتی به فیلمها یا موزیکها فکر میکنیم، همیشه این سوال پیش میاد که چرا باید منتظر بمونیم تا کل فایل دانلود شه؟ ایدهی استریمینگ هم از همین نیاز به دست اومد. این ایده به مرور زمان در زمینههای مختلف از جمله وب و برنامهنویسی هم کاربرد پیدا کرد. به عبارتی، استریمینگ راهی شد برای ارسال تدریجی دادهها از سرور به کلاینت.
تصور کنید یه صفحه وب دارید که چند تا بخش داره؛ یکی از بخشها خیلی سریع آماده میشه و یکی داره دادههای سنگینی رو لود میکنه. با استریمینگ، بخشهای آماده بلافاصله نمایش داده میشن و فقط اون بخشها با داده های سنگین منتظر میمونن.

معمولاً در روشهای سنتی Server-Side Rendering (SSR)، کل صفحه در سرور ساخته و سپس به مرورگر ارسال میشه. این یعنی کاربر هیچی نمیبینه تا زمانی که کل محتوا آماده بشه. Streaming این مشکل رو حل میکنه!
✅ مزایای Streaming :
1. لود سریعتر صفحه: کاربرها میتونن قسمتهایی از صفحه را ببینن، حتی اگر همه دادهها هنوز آماده نشده باشه.
2. تجربه کاربری بهتر: حس بهتری به کاربر میده که وبسایت سریع و پویا هست .
3. بهینهسازی سرور: فشار کمتری روی سرور وارد میشه، به این دلیل که دادهها بهصورت مرحلهای ارسال میشن.
1. استریمینگ ممکنه روی سئو تأثیر منفی بذاره، اما بستگی به شرایط داره
بعضی موتورهای جستجو مثل Yandex و Baidu نمیتونن صفحات رندرشده در سمت کلاینت رو بخونن، در حالی که Google میتونه. (این کار زمان بر و هزینه بره)
2. گوگل دو مرحلهای صفحات رو پردازش میکنه
ابتدا HTML خام رو میگیره (بدون اجرای جاوااسکریپت) بعداً جاوااسکریپت رو اجرا میکنه تا محتوای نهایی رو ببینه ، اما مسائل و چالشهای متعددی در این زمینه وجود داره که میتونه باعث تأخیر یا مشکلات در ایندکسینگ بشه.
3. استریمینگ ممکنه باعث بشه بعضی از محتواها خارج از کانتکست اصلی HTML نمایش داده بشن.
بعضی از محتواهای استریمشده در <div hidden> ذخیره میشن و ممکنه در ابتدا رندر نشن. این یعنی وقتی یک موتور جستجو صفحه رو بررسی میکنه، احتمال داره که محتوا رو نبینه، چون هنوز JS اجرا نشده.

در نتیجه اگه صفحات سایت شما استاتیک یا تغییراتشون کمه ، Streaming معمولاً مشکلی برای سئو ایجاد نمی کنه و حتی میتونه زمان بارگذاری رو کاهش بده، که به بهبود رتبهبندی کمک میکنه. اما اگه سایت شما داینامیکه و محتواش بهطور مداوم بهروزرسانی میشه، Streaming میتونه چالشهایی برای ایندکس شدن محتوا توسط موتورهای جستجو ایجاد کنه .
برای جلوگیری از این مشکلات، باید مطمئن شیم که محتوای کلیدی مثل تگهای متا، تایتل ها و اطلاعات ضروری درHTML اولیه رندر بشن تا Googlebot بتونه اونها رو سریعتر پردازش کنه. همچنین استفاده از Sitemap XML ، Robots.txt و بهینهسازی سرعت API ها میتونن به بهبود ایندکس شدن صفحات کمک کنن.
در مجموع، Streaming برای پروژههای Next.js که نیاز به نمایش سریع محتوا دارن، گزینهای قدرتمنده، اما باید با بهینهسازی سئو همراه باشه تا تأثیرات منفیش کاهش پیدا کنه.
دو روش اصلی پیادهسازی Streaming درNext.js
1. در سطح صفحه (Page Level):
در پوشه app/dashboard/، یک فایل جدید به نام loading.tsx ایجاد میکنیم:
export default function Loading() { return <div>Loading...</div>; }
در چارچوب Next.js ، فایل loading.tsx یک فایل ویژه است که بر پایه قابلیت «Suspense» در React ساخته شده. این فایل به ما این امکان رو میده تا یک رابط کاربری جایگزین (Fallback UI) ایجاد کنیم که در حین بارگذاری محتوای صفحه، بهجای محتوای اصلی نمایش داده بشه.
خب حالا میتونیم در فایل loading.tsx از ui های سفارشی و یا اسکلتون به عنوان جایگزین انتظار برای محتوا استفاده کنیم.
import DashboardSkeleton from '@/app/ui/skeletons'; export default function Loading() { return <DashboardSkeleton />; }
2. در سطح کامپوننت (Component Level):
با استفاده از قابلیت Suspense در React، میتونیم بخشهای خاصی از صفحه رو به صورت جداگانه استریم کنیم. برای مثال، اگه درخواست دادهای مثل ( )fetchRevenue زمانبر باشه، میتونیم اون رو به کامپوننت مربوطه منتقل کنیم و با استفاده از Suspense ، یک کامپوننت جایگزین (Fallback) در حین بارگذاری نمایش بدیم. در فایل /app/dashboard/page.tsx ، کامپوننت <RevenueChart> رو با Suspense و یک کامپوننت جایگزین مثل <RevenueChartSkeleton> رپ میکنیم:
// /app/dashboard/page.tsx import { Suspense } from 'react'; import RevenueChart from '@/app/ui/dashboard/revenue-chart'; import LatestInvoices from '@/app/ui/dashboard/latest-invoices'; import { RevenueChartSkeleton } from '@/app/ui/skeletons'; export default async function Page () { //other codes... return ( <main> {/*other components */} <div className="mt-6 grid grid-cols-1 gap-6 md:grid-cols-4 lg:grid-cols-8"> <Suspense fallback={<RevenueChartSkeleton />}> <RevenueChart /> </Suspense> <LatestInvoices latestInvoices={latestInvoices} /> </div> </main> ); }
سپس، در کامپوننت <RevenueChart>، دادههای مورد نیاز رو مستقیماً در داخل کامپوننت بارگذاری میکنیم:
import { generateYAxis } from '@/app/lib/utils'; import { CalendarIcon } from '@heroicons/react/24/outline'; import { lusitana } from '@/app/ui/fonts'; import { fetchRevenue } from '@/app/lib/data'; // ... export default async function RevenueChart() { // Make component async, remove the props const revenue = await fetchRevenue(); // Fetch data inside the component const chartHeight = 350; const { yAxisLabels, topLabel } = generateYAxis(revenue); if (!revenue || revenue.length === 0) { return <p className="mt-4 text-gray-400">No data available.</p>; } return ( // ... ); }
با این کار، اطلاعات داشبورد بلافاصله نمایش داده میشن، در حالی که یک اسکلتون برای <RevenueChart> در حین بارگذاری دادهها نمایش داده میشه.
نکته :
در اعمال streaming در روش اعمال بر روی یک صفحه یا page اگر فایل loading.tsx در سطحی بالاتر از صفحات دیگه مانند /invoices/page.tsx و /customers/page.tsx قرار داشته باشه، بهطور پیشفرض به اون صفحات هم اعمال میشه.
برای جلوگیری از این موضوع، میتونیم از Route Groups استفاده کنیم.
یک پوشه جدید به نام (overview) در داخل پوشه dashboard ایجاد میکنیم و فایلهای loading.tsx و page.tsx رو به پوشه منتقل میکنیم. با این کار، فایل loading.tsx فقط به صفحه مورد نظرمون اعمال میشه.

امیدوارم این مطلب مفید واقع شده باشه .
از نظرات و فیدبک هاتون خوشحال میشم ;)