Prefetching و رفتار Navigation
در درسهای قبل با Link و useRouter بین صفحات جابهجا شدیم و دیدیم ناوبری بدون بارگذاری کامل صفحه انجام میشود. حالا میخواهیم بفهمیم پشت این سرعت چه اتفاقی میافتد. Next.js پیش از اینکه کاربر روی لینکی کلیک کند، بخشی از مسیر مقصد را در پسزمینه بارگیری میکند (Prefetching) و هنگام کلیک، فقط بخش تغییرکردهی صفحه را عوض میکند. در این درس این رفتار را میشناسیم، میفهمیم چرا مسیرهای پویا گاهی کند به نظر میرسند و ابزارهایی برای بهبود تجربهی کاربر یاد میگیریم.
ناوبری در Next.js چگونه کار میکند؟
وقتی روی لینک معمولی کلیک میکنید، مرورگر کل سند HTML را دوباره میگیرد. در Next.js چهار سازوکار با هم کار میکنند:
- رندر روی سرور: مسیرها بهصورت پیشفرض روی سرور رندر میشوند.
- Prefetching: مسیرهایی که با
Linkدر دید کاربر (Viewport) قرار میگیرند، پیشاپیش و در پسزمینه بارگیری میشوند. - Streaming: بخشهای آمادهی صفحه میتوانند زودتر برسند و بخشهای دیرتر بعداً کامل شوند. جزئیات در فصل «Loading، Error و Streaming».
- ناوبری سمت کلاینت: بهجای بارگذاری کامل، فقط بخشهای تغییرکرده عوض میشوند و Layoutهای مشترک (که در درس «Nested Layoutها» دیدیم) حفظ میشوند.
نتیجه این است که اگر لینکها درست بنویسید، جابهجایی بین صفحات تقریباً فوری حس میشود.
Prefetching چیست؟
وقتی یک Link وارد محدودهی دید کاربر میشود (در ابتدا یا هنگام اسکرول)، Next.js در پسزمینه دادهی مسیر مقصد را درخواست میکند و آن را در حافظهی مرورگر نگه میدارد. وقتی کاربر کلیک میکند، بخش زیادی از کار از قبل انجام شده است.
مقدار پیشواکشی به نوع مسیر مقصد بستگی دارد:
| نوع مسیر مقصد | رفتار Prefetch پیشفرض |
|---|---|
| مسیر استاتیک (Static) | کل مسیر پیشاپیش بارگیری میشود. |
مسیر پویا (Dynamic) با فایل loading.tsx |
تا نزدیکترین loading.tsx بهصورت جزئی بارگیری میشود؛ یعنی Layoutهای مشترک و اسکلت انتظار از قبل آمادهاند. |
مسیر پویا بدون loading.tsx |
Prefetch رد میشود یا فقط بخش کمی از آن انجام میشود. |
دلیل این تفاوت منطقی است: مسیر استاتیک از قبل آماده است و بارگیری پیشاپیشاش ارزان است، ولی مسیر پویا باید برای هر درخواست روی سرور ساخته شود و پیشواکشی کامل آن کار اضافهی بیمورد روی سرور میگذارد، چون شاید کاربر هرگز آن لینک را باز نکند.
مسیر «پویا» در اینجا یعنی صفحهای که برای هر درخواست رندر میشود، مثلاً چون searchParams یا cookies() میخواند. این با «مسیری که قطعهی پویای [slug] دارد» فرق دارد: مسیر /posts/[slug] با generateStaticParams میتواند از قبل ساخته شود. تفاوت دقیق رندر پویا و استاتیک را در فصل «Rendering و Data Fetching» میبینیم.
دو نکته دربارهی نسخهی توسعه
- Prefetch فقط در Production فعال است. در حالت
npm run devاین رفتار را مثل واقعیت نمیبینید. برای دیدنش بایدnpm run buildوnpm run startرا اجرا کنید. - Next.js 16 سیستم Prefetch را بازنویسی کرده است: Layoutی که بین چند مسیر مشترک است فقط یکبار دانلود میشود و فقط بخشهایی که هنوز در حافظه نیستند پیشواکشی میشوند. اینها خودکار هستند و کدی برای فعالکردنشان لازم نیست. ممکن است تعداد درخواستهای Prefetch بیشتر ولی حجم هرکدام کمتر دیده شود.
کنترل Prefetch با ویژگی prefetch
کامپوننت Link ویژگی prefetch دارد که سه حالت دارد:
| مقدار | معنی |
|---|---|
| بدون مقدار (پیشفرض) | Next.js بر اساس نوع مسیر تصمیم میگیرد (جدول بالا). |
prefetch={true} |
همیشه کل مسیر و دادهاش پیشاپیش بارگیری میشود. |
prefetch={false} |
Prefetch غیرفعال است، هم برای ورود به Viewport و هم هنگام قرارگرفتن ماوس روی لینک. |
<Link href="/posts">نوشتهها</Link>
<Link href="/dashboard" prefetch={true}>داشبورد</Link>
<Link href="/about" prefetch={false}>درباره</Link>
در بیشتر موارد رفتار پیشفرض بهترین انتخاب است. prefetch={false} برای وقتی مفید است که صفحه لینکهای خیلی زیادی دارد (مثل جدولی با صدها ردیف) و نمیخواهید همهی آنها درخواست پسزمینه ایجاد کنند. prefetch={true} هم فقط برای لینکهای بسیار مهمی که احتمال کلیکشان زیاد است منطقی است، چون بار بیشتری روی سرور میگذارد.
ابزار دستی هم داریم: router.prefetch(href) که در درس «مسیریابی برنامهای با useRouter» دیدیم. این روش فقط برای موارد خاص (مثلاً پیشواکشی بعد از یک رویداد) کاربرد دارد.
نقش loading.tsx در سرعت ناوبری
فایل loading.tsx که در درس «فایلهای ویژهی App Router» معرفی کردیم، برای مسیرهای پویا یک نقش مضاعف دارد:
- هنگام کلیک، اسکلت انتظار فوراً نشان داده میشود و به کاربر میگوید ناوبری شروع شده است.
- باعث میشود Next.js بتواند مسیر پویا را تا همان مرز بهصورت جزئی پیشواکشی کند.
بدون این فایل، کلیک روی لینک یک مسیر پویا ممکن است تا رسیدن پاسخ سرور هیچ تغییری روی صفحه ایجاد نکند و کاربر فکر کند برنامه قفل کرده است. توصیهی Next.js این است که برای مسیرهای پویا loading.tsx اضافه شود. نحوهی ساخت و طراحی آن را در درس «فایل loading.tsx» و «طراحی وضعیتهای انتظار و خطا در DevNotes» انجام میدهیم؛ فعلاً فقط بدانید چنین ابزاری برای این مشکل وجود دارد.
شبکهی کند و useLinkStatus
حتی با Prefetch، در شبکهی کند یا ناپایدار ممکن است پیشواکشی تا لحظهی کلیک تمام نشود. در این حالت کاربر کلیک میکند و تا آمادهشدن مسیر هیچ بازخوردی نمیبیند. Hook با نام useLinkStatus وضعیت «در حال ناوبری» (pending) یک لینک را میدهد تا بتوانیم یک نشانهی بصری نشان دهیم.
- این Hook را از
next/linkوارد میکنیم. - باید داخل کامپوننتی استفاده شود که درون خود
Linkرندر میشود؛ چون وضعیت همان لینکِ والد را گزارش میدهد. - فقط در Client Component کار میکند و در Pages Router همیشه
{ pending: false }برمیگرداند. - مقدار
pendingبرابرtrueمیشود وقتی پیشواکشی غیرفعال یا هنوز در جریان است و ناوبری منتظر آن مانده است، یا وقتی مسیر مقصد پویا است وloading.tsxندارد. - مستندات رسمی آن را یک «وصلهی سریع» میداند: اگر ناوبری کند است، ریشهی مشکل را با Prefetch یا
loading.tsxحل کنید.
عملی: نشانهی انتظار در منوی DevNotes
فایل متأثر: components/NavLink.tsx که در درس «لینکسازی با Link» ساختیم. یک کامپوننت کوچک PendingHint اضافه میکنیم و آن را داخل Link قرار میدهیم:
// components/NavLink.tsx
"use client";
import type { ReactNode } from "react";
import Link, { useLinkStatus } from "next/link";
import { usePathname } from "next/navigation";
type NavLinkProps = {
href: string;
children: ReactNode;
};
// باید داخل Link رندر شود تا وضعیت همان لینک را بخواند.
function PendingHint() {
const { pending } = useLinkStatus();
return <span aria-hidden="true">{pending ? " …" : null}</span>;
}
export default function NavLink({ href, children }: NavLinkProps) {
const pathname = usePathname();
const isActive =
href === "/"
? pathname === "/"
: pathname === href || pathname.startsWith(`${href}/`);
return (
<Link href={href} aria-current={isActive ? "page" : undefined}>
{children}
<PendingHint />
</Link>
);
}توضیح کد
PendingHintیک کامپوننت جدا است چونuseLinkStatusباید در کامپوننتِ فرزند لینک فراخوانی شود، نه در همان کامپوننتی که خودLinkرا میسازد.- وقتی
pendingدرست باشد، یک «…» کنار متن لینک ظاهر میشود. aria-hidden="true"باعث میشود صفحهخوانها این نشانهی تزئینی را نخوانند.- بقیهی کد همان است که از قبل داشتیم و لینک فعال را با
aria-currentعلامت میزند.
در شبکهی سریع و روی سیستم خودتان این نشانه تقریباً هیچوقت دیده نمیشود. برای دیدنش در ابزار توسعهی مرورگر، تب Network، گزینهی کندکردن شبکه (مثلاً Slow 4G یا Fast 3G) را فعال کنید و روی لینکی کلیک کنید که هنوز پیشواکشی نشده است.
عملی: دیدن Prefetch در Production
برای مشاهدهی واقعی Prefetch این مراحل را انجام دهید:
- سرور توسعه را با
Ctrl + Cمتوقف کنید. - دستور
npm run buildرا اجرا کنید و منتظر پایان بماند. - دستور
npm run startرا بزنید وhttp://localhost:3000را باز کنید. - ابزار توسعهی مرورگر را باز کنید، به تب Network بروید و فیلتر
Fetch/XHRرا انتخاب کنید. - صفحه را ریفرش کنید. بدون هیچ کلیکی، درخواستهایی برای مسیرهایی که در هدر لینک شدهاند ظاهر میشود؛ اینها درخواستهای Prefetch هستند و معمولاً پارامتر
_rscدر آدرسشان دارند. - حالا روی «نوشتهها» کلیک کنید. جابهجایی سریع انجام میشود و صفحه بهطور کامل از اول بارگذاری نمیشود.
فرمت دقیق درخواستها و نامها به نسخهی فرعی Next.js بستگی دارد؛ مهم این است که پیش از کلیک، درخواستهای پسزمینه میبینید.
سایر رفتارهای ناوبری که خوب است بدانید
-
حفظ Layoutها: هنگام جابهجایی بین صفحاتی که Layout مشترک دارند، آن Layout دوباره ساخته نمیشود و State کامپوننتهای کلاینتی داخلش حفظ میشود. اگر نیاز دارید State با هر ناوبری ریست شود، از
template.tsxاستفاده میشود که در درس «فایلهای ویژهی App Router» معرفی کردیم. - دکمههای بازگشت و جلو: مرورگر هنگام برگشت به صفحات قبلی، معمولاً وضعیت قبلی را برمیگرداند و Next.js آن را با تاریخچه هماهنگ نگه میدارد. بخشی از نتیجهی ناوبری در حافظهی مرورگر (Client-side Router Cache) نگه داشته میشود. طول عمر آن و ارتباطش با بقیهی انواع کش را در درس «تفاوت کش داده، خروجی رندر، Router Cache، Request Memoization و کش مرورگر» بررسی میکنیم.
-
محل اسکرول: بهطور پیشفرض بعد از ناوبری به بالای صفحهی جدید میرویم. با
scroll={false}درLinkیا گزینهی{ scroll: false }درrouter.pushاین رفتار خاموش میشود. بعد از ناوبری با دکمهی بازگشت، موقعیت اسکرول قبلی بازیابی میشود. در Next.js 16، اگر در CSS برایhtmlمقدارscroll-behavior: smoothگذاشتهاید و میخواهید در ناوبری هم اعمال شود، باید ویژگیdata-scroll-behavior="smooth"را روی تگhtmlبگذارید. -
History API بومی: میتوان از
window.history.pushStateوreplaceStateمستقیماً استفاده کرد؛ این فراخوانیها باusePathnameوuseSearchParamsهماهنگ میشوند. برای نیازهای معمولی ما،LinkوuseRouterکافی هستند.
وضعیت مسیرهای DevNotes
با در نظر گرفتن حالت پیشفرض (بدون cacheComponents) و ساختاری که تا اینجا ساختهایم:
| مسیر | وضعیت فعلی | اثر بر Prefetch |
|---|---|---|
/ و /about |
استاتیک | کل مسیر پیشاپیش بارگیری میشود |
/posts/[slug] |
با generateStaticParams برای slugهای نمونه از قبل ساخته میشود |
برای این slugها مثل مسیر استاتیک رفتار میکند |
/posts |
پویا، چون searchParams میخواند |
فقط با loading.tsx بهصورت جزئی پیشواکشی میشود |
/dashboard و زیرمسیرها |
فعلاً استاتیک (دادهی کاربر نداریم) | بعد از افزودن احراز هویت پویا میشوند |
این جدول نشان میدهد چرا در فصل «Loading، Error و Streaming» برای /posts و داشبورد حتماً loading.tsx میسازیم. هنوز آن فایل را نساختهایم؛ فعلاً کافی است ارتباط را بدانید.
نکتههای مهم
-
برای مقایسهی سرعت از Production استفاده کنید: آنچه در
npm run devمیبینید، از نظر Prefetch و سرعت ناوبری، نمایندهی تجربهی واقعی کاربر نیست. -
لینک داخلی را با Link بنویسید: تگ
<a>معمولی نه Prefetch دارد و نه ناوبری سمت کلاینت. - Prefetch را بیدلیل خاموش یا اجباری نکنید: رفتار پیشفرض برای بیشتر لینکها مناسب است.
-
useLinkStatusعلاج اصلی نیست: اگر ناوبری کند است، اول ببینید مسیر پویاloading.tsxدارد یا نه و آیا میتواند استاتیک شود. نشانهی انتظار فقط کمک تجربهی کاربری است. - Prefetch مقصد را امن نمیکند: پیشواکشی یک مسیر خصوصی به معنی دسترسی آزاد نیست؛ هر درخواست باید در سرور مجوز بگیرد. این موضوع را در فصل «Authentication و Security» میبینیم.
جمعبندی
- Next.js لینکهایی که با
Linkدر Viewport قرار میگیرند را در Production پیشاپیش بارگیری میکند و ناوبری را سریعتر میکند. - مسیرهای استاتیک کامل پیشواکشی میشوند؛ مسیرهای پویا یا رد میشوند یا فقط تا نزدیکترین
loading.tsxبهصورت جزئی پیشواکشی میشوند. - ویژگی
prefetch(مقدارهای پیشفرض،trueوfalse) و متدrouter.prefetchاین رفتار را کنترل میکنند. - اضافهکردن
loading.tsxبرای مسیرهای پویا ناوبری را فوری حسپذیر و Prefetch جزئی را ممکن میکند. useLinkStatusدر شبکهی کند، وضعیت «در حال ناوبری» یک لینک را برای نمایش بازخورد میدهد.- در DevNotes،
NavLinkرا با یک نشانهی انتظار بهروز کردیم. - در درس بعد، «ساخت فهرست و صفحهی جزئیات نوشتهها»، آنچه در این فصل یاد گرفتیم را برای ساخت فهرست و صفحهی جزئیات نوشتهها به کار میبریم.
