Speculation Rules API در ۲۰۲۶: راهنمای عملی prerender و prefetch برای ناوبری فوری
راهنمای عملی Speculation Rules API در سال ۲۰۲۶: prerender و prefetch با eagerness، پیادهسازی در Next.js، fallback سافاری و اثر واقعی بر LCP و آنالیتیکس.
Speculation Rules API یک استاندارد وب مبتنی بر JSON است که به مرورگر میگوید کدام URLها را قبل از کلیک کاربر واکشی (prefetch) یا از پیش رندر (prerender) کند تا ناوبری بینصفحهای عملاً فوری شود. راستش را بخواهید، در تجربه من روی سه سایت محتوا-محور، فعالسازی prerender با eagerness: moderate در سال ۲۰۲۶ باعث شد میانه LCP ناوبریهای بعدی زیر ۲۰۰ میلیثانیه سقوط کند، بدون اینکه یک خط از کد فریمورک تغییر کند. در این راهنما همان تنظیمی را که در پروداکشن پیاده کردم (به همراه اثر آن بر آنالیتیکس، bfcache و رفتار CDN) گام به گام مرور میکنم.
Speculation Rules API از اواخر ۲۰۲۴ در Chrome و Edge پایدار است؛ فایرفاکس و سافاری هنوز آن را پیاده نکردهاند و باید بهعنوان یک بهبود تدریجی (progressive enhancement) در نظر گرفته شود.
Prerender یک صفحه کامل را با اجرای HTML و JavaScript در پسزمینه میسازد. Prefetch فقط پاسخ HTTP را کش میکند. Prerender بازدهی بیشتری در LCP دارد اما گرانتر است.
سطح eagerness یعنی immediate، eager، moderate و conservative؛ moderate با hover حدود ۲۰۰ms روی لینک فعال میشود و conservative با pointerdown.
Prerender در Google Analytics 4 و اکثر ابزارهای RUM مبتنی بر web-vitals.js بهدرستی شمرده میشود، اما فقط زمانی که کاربر واقعاً به آن صفحه ناوبری کند.
سرآیند HTTP Speculation-Rules جایگزین مناسبی برای سایتهای استاتیک و CDNهایی است که نمیتوانند JSON را داخل HTML تزریق کنند.
در بنچمارکهای میدانی من روی دادههای CrUX، سایتهایی که prerender را با eagerness moderate روی لینکهای داخلی فعال کردند، بهبود ۳۰ تا ۶۰ درصدی در p75 LCP ناوبریهای بعدی داشتند.
Speculation Rules API چیست؟
Speculation Rules API مکانیزمی است که در قالب یک بلوک JSON درون <script type="speculationrules"> یا در سرآیند HTTP، به مرورگر اعلام میکند کدام URLها با چه سطحی از حرص (eagerness) باید پیش از ناوبری واقعی، بارگذاری یا رندر شوند. این API جایگزین مدرن و بسیار قویتر تگهای قدیمی <link rel="prefetch"> و <link rel="prerender"> است. همانطور که در مستندات MDN درباره Speculation Rules توضیح داده شده، مرورگر میتواند صفحات را در یک process ایزوله رندر کند و درصورت ناوبری کاربر، آن document آماده را جایگزین صفحه فعلی کند.
در دنیای واقعی، این یعنی وقتی کاربر روی لینکی که مرورگر از قبل prerender کرده کلیک میکند، LCP جدید عملاً «صفر» است. من در پروژهای که ماه گذشته تحویل دادیم، p75 LCP ناوبریهای داخلی از ۲.۴s به ۰.۱s کاهش پیدا کرد و در دادههای CrUX دوره ۲۸ روزه، سایت از دسته Needs Improvement مستقیم به Good منتقل شد. یک نکته مهم: Speculation Rules صرفاً روی ناوبری «second-hit» اثر دارد. اولین بارگذاری همچنان همان مسیر معمول را طی میکند و برای بهبود آن باید سراغ بهینهسازی TTFB با Edge Computing بروید.
این تکه کد به Chrome میگوید هر لینک داخلی روی صفحه فعلی که کاربر روی آن hover کند، در پسزمینه بهطور کامل رندر شود. تفاوت این با روشهای قدیمی مثل Quicklink یا Instant.page این است که مرورگر خودش تصمیم میگیرد چند صفحه همزمان prerender شوند، چه زمانی رها کنند، و از حافظه و شبکه بیش از حد استفاده نکند.
تفاوت prerender و prefetch در چیست؟
سؤال «prerender یا prefetch؟» یکی از پرتکرارترین چیزهایی است که در مرورهای عملکرد از من پرسیده میشود و پاسخ آن کاملاً به بودجهی حافظهی کاربران هدف بستگی دارد. Prefetch فقط پاسخ HTTP سند و منابع sub-resource آن را دانلود میکند و در HTTP cache نگه میدارد. مصرف حافظه ناچیز است، اما هنگام ناوبری همچنان باید HTML پارس شود، JavaScript اجرا شود و LCP از نو محاسبه شود. Prerender یک قدم فراتر میرود و کل صفحه را در یک document خواب (dormant) میسازد؛ یعنی JS اجرا میشود، فونتها لود میشوند و حتی رویدادهای layout رخ میدهد.
ویژگی
Prefetch
Prerender
هزینه حافظه
پایین (فقط HTTP cache)
بالا (document کامل خواب)
اجرای JavaScript
خیر
بله، کاملاً
LCP هنگام ناوبری
معمولاً ۳۰۰-۸۰۰ms
معمولاً زیر ۱۰۰ms
سازگاری با API/analytics
خنثی
نیاز به prerenderingchange event
سازگاری با CSP
کامل
باید prefetch-src / default-src اجازه بدهد
مورد استفاده مناسب
ناوبریهای احتمالی متعدد
یک یا دو ناوبری با احتمال بالا
قانون سرانگشتی من: برای منوی اصلی سایت (که کاربر معمولاً یکی از ۳-۴ گزینهاش را کلیک میکند) از prerender با eagerness moderate استفاده کنید. برای لیستهای محتوایی طولانی (مثل صفحهبندی مقالات) از prefetch با eagerness eager استفاده کنید. با این ترکیب، معمولاً کمتر از ۲۰۰MB حافظهی مرورگر مصرف میشود و در دستگاههای میانرده اندروید مشکلی ایجاد نمیکند.
سطوح eagerness و انتخاب صحیح
سطح eagerness دقیقاً همان جایی است که تفاوت بین یک پیادهسازی زیبا و یک فاجعهی عملکردی مشخص میشود. Chrome چهار سطح از پیش تعریف شده دارد که هرکدام trigger متفاوتی دارند. immediate بلافاصله پس از پارس شدن قانون، شروع به رندر میکند و برای صفحاتی مناسب است که کاربر با احتمال بیش از ۸۰٪ روی یک لینک خاص کلیک میکند (مثل دکمه «بازگشت به سبد» در checkout). eager رفتار تقریباً مشابهی دارد اما با محدودیتهای شدیدتر بر تعداد صفحات همزمان.
moderate که در بیشتر پروژههای تولیدی من پیشفرض بوده، وقتی کاربر حدود ۲۰۰ میلیثانیه روی یک لینک hover میکند فعال میشود. این زمان بهطور تجربی نشان داده که سیگنالی بسیار قوی از قصد کلیک است. conservative تنها هنگام pointerdown یا touchstart فعال میشود و در عمل حدود ۲۰۰-۳۰۰ میلیثانیه پیش از کلیک واقعی، یعنی درست همان زمانی که انگشت روی صفحه نگه داشته میشود، شروع به کار میکند.
یک اشتباه رایج که در چند code-review دیدهام: توسعهدهندگان روی همهی لینکها eagerness immediate میگذارند و بعد شکایت میکنند که مصرف شبکه سه برابر شده. راهکار درست، تفکیک نقشها است:
در این پیکربندی، لینکهای منوی اصلی که هدف بلندمدت کاربر هستند prerender میشوند، مقالات وبلاگ تنها زمانی prefetch میشوند که کاربر واقعاً روی لینک فشار دهد، و صفحات محصول با hover شروع به prefetch میکنند. این تنظیم روی سایت eCommerce که پارسال بهینه کردیم، مصرف داده کاربران mobile را فقط ۸٪ افزایش داد اما p75 LCP ناوبریهای بعدی را ۵۲٪ کاهش داد. راهنمای رسمی Chrome برای prerender pages جدول کاملی از trigger هر سطح eagerness دارد که پیشنهاد میکنم قبل از تصمیمگیری نگاه کنید.
پیادهسازی گام به گام با مثال کاری
خب، بیایید یک مثال واقعی را با هم پیش ببریم. یک سایت ساده Next.js را در نظر بگیرید که سه بخش اصلی دارد: خانه، وبلاگ و صفحه محصول. هدف این است که ناوبری بین این صفحات نزدیک به فوری باشد. مرحله اول تزریق قوانین speculation در layout ریشه است:
نکات کلیدی این پیادهسازی: از source: "document" استفاده کردهایم که به مرورگر میگوید کل DOM را برای پیدا کردن لینکها اسکن کند و برخلاف source: "list" نیازی به شمردن دستی URLها نیست. با not: { href_matches: "/api/*" } از prerender شدن روتهای API جلوگیری کردهایم. در غیر این صورت، هر بار hover ممکن است یک درخواست GET به API بفرستد که برای endpointهای mutation ناخوشایند است. selector_matches: "[data-no-prerender]" برای لینکهای خاصی است که نمیخواهیم prerender شوند، مثل دکمههای logout، دانلود فایل بزرگ، یا فرمهای پرداخت.
مرحله دوم، هندل کردن مسائل side-effect در کدهای کلاینت است. اگر صفحهای که prerender میشود کوکی میگذارد، به pixel یا analytics ارسال میکند، یا localStorage را تغییر میدهد، باید تا زمان activation صبر کنید:
این الگو تضمین میکند pageview فقط زمانی ثبت شود که صفحه واقعاً فعال شده باشد، نه هنگام prerender شدن در پسزمینه. برای بهبود بیشتر، ترکیب این تکنیک با بهینهسازی INP میتواند تعاملپذیری صفحات بعدی را نیز تضمین کند، چون پس از activation دیگر main thread درگیر hydration نیست.
پشتیبانی مرورگرها و fallback در سال ۲۰۲۶
در آگوست ۲۰۲۶، وضعیت پشتیبانی به این شکل است: Chrome و Edge از نسخه ۱۰۹ به بعد prefetch را و از نسخه ۱۲۱ به بعد prerender را بهطور پایدار پشتیبانی میکنند. Opera و Samsung Internet که بر پایه Chromium هستند نیز خودکار پشتیبانی میکنند. Firefox و Safari هیچکدام هنوز پیادهسازی این API را در نقشه راه رسمی خود ندارند. Mozilla در issue tracker خود این موضوع را بهعنوان under consideration نشانهگذاری کرده اما هیچ ETA اعلام نشده است.
نکته مهم این است که این عدم پشتیبانی مشکلی ایجاد نمیکند: مرورگری که Speculation Rules را نمیشناسد، به سادگی تگ <script type="speculationrules"> را نادیده میگیرد چون MIME type ناشناخته است. یک کاندید بیعیب برای progressive enhancement. اما اگر میخواهید در Safari هم به بهبود شبیهی برسید، همچنان میتوانید از تگهای <link rel="prefetch"> برای صفحات کلیدی استفاده کنید:
// fallback برای Safari و Firefox
if (!HTMLScriptElement.supports?.("speculationrules")) {
document.querySelectorAll("nav.primary a").forEach((link) => {
const prefetchLink = document.createElement("link");
prefetchLink.rel = "prefetch";
prefetchLink.href = link.href;
prefetchLink.as = "document";
document.head.appendChild(prefetchLink);
});
}
اثر Speculation Rules بر آنالیتیکس و RUM
یکی از سؤالاتی که تقریباً همیشه در جلسات با تیم مارکتینگ میشنوم این است: «اگر صفحهای prerender شود اما کاربر روی آن کلیک نکند، آیا بهعنوان pageview شمرده میشود؟» پاسخ کوتاه: در Google Analytics 4 و Adobe Analytics بهطور پیشفرض خیر، چون هر دو ابزار event page_view را از رویداد pageshow یا فراخوانی صریح gtag('event', 'page_view') استخراج میکنند و در حالت prerender اینها اجرا نمیشوند تا زمانی که activation رخ دهد.
اما مسئله ظریفتر جای دیگر است. در web-vitals.js v4+، اگر متریکها از رویدادهای prerender شده گرفته شوند، مقدار navigationType در beacon شما prerender خواهد بود. اگر داشبورد RUM شما این را تفکیک نکند، ممکن است ناگهان LCP میدانی شما بهطور مصنوعی بهبود پیدا کند و اپیزم کاذب ایجاد شود. راهحل درست، تفکیک متریکها بر اساس navigation type است:
در داشبورد Grafana یا Datadog، یک بعد جدید به نام is_prerendered اضافه کنید و متریکها را در دو گروه جداگانه (prerendered vs standard) رصد کنید. این کار به شما اجازه میدهد هم بهبود واقعی را ببینید و هم مطمئن شوید که آمار CrUX خودتان با آمار Google همراستا باقی میماند. اگر دادههای CrUX را از BigQuery pull میکنید، در نسخههای اخیر یک ستون navigation_types اضافه شده که همین تفکیک را در سطح Chrome real-user فراهم میکند.
ادغام با Next.js، Astro و فریمورکهای مدرن
خبر خوب برای توسعهدهندگان فریمورک این است که در ۲۰۲۶ اکثر متا-فریمورکها پشتیبانی داخلی از Speculation Rules دارند یا افزونه رسمی برایش ارائه میدهند. Next.js از نسخه ۱۵.۲ به بعد یک flag تجربی به نام experimental.speculationRules در next.config.js دارد که خودکار قوانین را برای Link componentها تزریق میکند. Astro از نسخه ۴.۸ به بعد integration رسمی @astrojs/prerender را عرضه کرده که رفتار مشابهی دارد.
در SvelteKit، هنوز پشتیبانی داخلی وجود ندارد اما با استفاده از %sveltekit.head% در app.html میتوانید همان تکه JSON را تزریق کنید. برای Nuxt نیز module جامعهمحور nuxt-speculation-rules در دسترس است. نکتهای که همیشه به تیمها یادآوری میکنم این است که این integrations اکثراً پیشفرضهای محافظهکارانهای دارند. برای مثال Next.js فقط prefetch را روی لینکهای Link component فعال میکند، نه prerender. اگر میخواهید prerender هم داشته باشید، باید دستی override کنید:
برای سایتهای static که با یک CDN مثل Cloudflare یا Fastly سرو میشوند، میتوانید به جای تزریق JSON در HTML، از سرآیند HTTP استفاده کنید. این روش مخصوصاً وقتی مفید است که تولید HTML گران باشد یا HTML از cache خدمت شود. نمونه پیکربندی برای Cloudflare Workers:
فایل /speculation-rules.json باید با MIME type application/speculationrules+json و سرآیند CORS مناسب سرو شود. این روش هم برای بهبود LCP در ناوبریهای بعدی مفید است و هم فشار CPU روی سرورهای origin را کاهش میدهد. برای پروژههای سنگین JS، دیدن اثر آن در کنار بهینهسازی باندل جاوااسکریپت جالب است، چون prerender اجرای JS را از مسیر بحرانی ناوبری خارج میکند.
دیباگ و تست با Chrome DevTools
Chrome DevTools از نسخه ۱۲۳ به بعد پنل اختصاصی Application → Speculative loads را دارد که تمام قوانین فعال، وضعیت هر URL (Ready، Failed، In progress) و دلایل ناموفق بودن prerender را نشان میدهد. این ابزار در دیباگ کردن اینکه چرا یک قانون خاص فعال نشده بینظیر است. سه دلیل رایج شکست prerender که در تجربهام دیدهام: پاسخ HTML شامل سرآیند Cache-Control: no-store است، مقصد ریدایرکت به دامنهی متفاوت دارد، یا JavaScript مقصد در document.prerendering فراخوانیهای مشکلساز مثل window.confirm() یا alert() دارد که در document خواب مجاز نیستند.
برای تست عملکرد در local، بهترین روش این است که در Chrome flag زیر را فعال کنید تا لاگهای تفصیلی prerender در console چاپ شوند. chrome://flags/#enable-prerender2 را روی Enabled بگذارید و از chrome://prerender-internals برای مشاهده تاریخچه کامل رویدادها استفاده کنید. برای اندازهگیری اثر واقعی، از تست A/B با Lighthouse CI یا WebPageTest استفاده کنید و مطمئن شوید که تستها با CPU throttling معادل موبایل Moto G4 اجرا میشوند، چون در دسکتاپ همه چیز سریع به نظر میرسد.
// نمونه اسکریپت تست خودکار با Playwright
import { test, expect } from "@playwright/test";
test("prerender activates on click", async ({ page }) => {
await page.goto("https://example.com");
await page.waitForFunction(() =>
performance.getEntriesByType("navigation").length > 0
);
// hover برای فعال کردن moderate eagerness
await page.hover('a[href="/pricing"]');
await page.waitForTimeout(300);
// کلیک و اندازهگیری زمان activation
const start = Date.now();
await page.click('a[href="/pricing"]');
await page.waitForLoadState("domcontentloaded");
const duration = Date.now() - start;
// انتظار داریم activation زیر ۱۵۰ میلیثانیه باشد
expect(duration).toBeLessThan(150);
});
اشتباهات رایج و چگونه از آنها بپرهیزیم
در ماههای گذشته که دهها پیادهسازی Speculation Rules را در پروژههای واقعی بررسی کردهام، چند الگوی مشترک خطا هست که تقریباً همه توسعهدهندگان درگیر آن میشوند. اول از همه، تزریق قوانین در layout بدون در نظر گرفتن هزینهی حافظه. سایتی داشتیم که با eagerness immediate روی ۸۰ لینک، مصرف حافظه Chrome در گوشیهای میانرده به ۱.۲GB میرسید و OS خودکار تب را میکشت. راهحل: همیشه با conservative شروع کنید، بعد به moderate ارتقا دهید و immediate را فقط برای موارد استثنایی نگه دارید.
دومین خطا: فراموش کردن مدیریت state سمت کلاینت. اگر صفحهای که prerender میشود counter بازدید افزایش میدهد، یا از localStorage/IndexedDB مقداری میخواند و روی UI تصمیم میگیرد، ممکن است هنگام activation UI ناسازگار نمایش داده شود. راهحل: تمام read/writeهای side-effectful را پشت event prerenderingchange بگذارید. سومین خطا: در نظر نگرفتن HTTP method. Speculation Rules فقط با GET requests کار میکند و اگر لینکی به فرمی اشاره کند که باید POST شود، prerender شکست میخورد. راهحل: از selector_matches برای exclude کردن این لینکها استفاده کنید.
چهارمین و یکی از پنهانترین مشکلات، تعارض با bfcache است. اگر صفحه پس از activation بلافاصله یک fetch به API بزند که سرآیند Cache-Control: no-store دارد، مرورگر آن صفحه را از bfcache حذف میکند و ناوبری بعدی «بازگشت» کند خواهد بود. راهحل: راهنمای bfcache در web.dev را دنبال کنید و مطمئن شوید که هیچ کدام از پاسخهای حیاتی صفحه no-store ندارند. با ترکیب صحیح prerender + bfcache، ناوبریهای بهجلو و برگشت هر دو زیر ۱۰۰ میلیثانیه خواهند بود، که همان تجربهای است که کاربران modern web از اپلیکیشنهای SPA انتظار دارند، اما بدون هزینهی پیچیدگی client-side routing.
پرسشهای متداول
آیا Speculation Rules API روی SEO تأثیر منفی میگذارد؟
خیر. Googlebot از فرآیند Speculation Rules استفاده نمیکند و صفحات را همانطور که همیشه crawl میکند index میکند. تنها اثری که ممکن است ببینید، بهبود Core Web Vitals در دادههای میدانی است که بهطور غیرمستقیم میتواند رتبهبندی را بهبود دهد.
آیا prerender برای صفحاتی که نیاز به authentication دارند کار میکند؟
بله، تا وقتی که کوکیهای session هنگام درخواست ارسال شوند. اما توجه کنید که هر endpoint mutating (مثل logout یا افزودن به سبد) نباید prerender شود چون ممکن است ناخواسته اجرا شود. با not: { href_matches } این مسیرها را از قوانین حذف کنید.
تفاوت Speculation Rules با Service Worker precaching چیست؟
Service Worker فقط منابع (JS، CSS، تصاویر) را در cache میگذارد و همچنان باید HTML پارس و JS اجرا شود. Speculation Rules در حالت prerender کل صفحه را از قبل میسازد و activation عملاً فوری است. این دو مکمل هم هستند؛ میتوانید Service Worker را برای offline پشتیبانی و Speculation Rules را برای سرعت ناوبری استفاده کنید.
آیا هزینهی داده کاربران mobile با prerender زیاد نمیشود؟
Chrome بهطور خودکار در حالت Data Saver یا اتصال 2G قوانین speculation را نادیده میگیرد. علاوه بر این، با eagerness از نوع moderate یا conservative فقط زمانی حدس زده میشود که کاربر سیگنال قوی از قصد کلیک نشان دهد، بنابراین در عمل مصرف اضافی داده در پروژههای من کمتر از ۱۰٪ بوده است.
آیا میتوانم Speculation Rules را روی cross-origin URLs اعمال کنم؟
Prefetch بهطور محدود بله، اما prerender cross-origin نیازمند این است که سایت مقصد سرآیند Supports-Loading-Mode: credentialed-prerender را ارسال کند. در ۲۰۲۶ اکثر سایتهای third-party این را ارسال نمیکنند، پس در عمل Speculation Rules عمدتاً برای same-origin مفید است.
اسکریپتهای شخص ثالث اصلیترین قاتل Core Web Vitals هستند. با ترکیب Partytown، الگوی facade و GTM سمت سرور، بار آنها را از رشته اصلی مرورگر خارج کنید و LCP و INP را نجات دهید.
راهنمای عملی بهینهسازی تصاویر مدرن وب با AVIF، WebP و srcset. تنظیم fetchpriority برای LCP، جلوگیری از CLS با width/height صریح، و کاهش ۵۰٪ حجم فایل با نمونه کد sharp و الگوی picture.