کش عقب / جلو

منتشر شده: ۲۵ مه ۲۰۲۳، آخرین به‌روزرسانی: ۲ ژوئیه ۲۰۲۶

حافظه پنهان Back/Forward (یا bfcache) یک بهینه‌سازی مرورگر است که امکان پیمایش سریع به عقب و جلو را فراهم می‌کند. این قابلیت به طور قابل توجهی تجربه مرور وب را بهبود می‌بخشد، به خصوص برای کاربرانی که شبکه‌ها یا دستگاه‌های کندتری دارند.

به عنوان توسعه‌دهندگان وب، بسیار مهم است که بدانید چگونه صفحات خود را برای bfcache بهینه کنید تا کاربران شما بتوانند از مزایای آن بهره‌مند شوند.

سازگاری با مرورگرها

همه مرورگرهای اصلی شامل bfcache هستند، از جمله کروم از نسخه ۹۶ به بعد، فایرفاکس و سافاری .

اصول اولیه bfcache

با استفاده از حافظه پنهان back/forward (bfcache)، به جای اینکه هنگام خروج کاربر از صفحه، آن را از بین ببریم، تخریب را به تعویق می‌اندازیم و اجرای JS را متوقف می‌کنیم. اگر کاربر به زودی به عقب برگردد، صفحه را دوباره قابل مشاهده می‌کنیم و اجرای JS را از حالت مکث خارج می‌کنیم. این منجر به پیمایش تقریباً فوری صفحه برای کاربر می‌شود.

چند بار شده که از یک وب‌سایت بازدید کرده‌اید و روی لینکی کلیک کرده‌اید تا به صفحه دیگری بروید، اما متوجه شده‌اید که این صفحه آن چیزی نیست که می‌خواستید و دکمه بازگشت را زده‌اید؟ در آن لحظه، bfcache می‌تواند تفاوت زیادی در سرعت بارگذاری صفحه قبلی ایجاد کند:

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

برای درک سرعتی که bfcache می‌تواند برای ناوبری به ارمغان بیاورد، این ویدیو از نحوه‌ی عملکرد آن را تماشا کنید:

استفاده از bfcache باعث می‌شود صفحات در هنگام پیمایش به عقب و جلو بسیار سریع‌تر بارگذاری شوند.

در ویدیو، مثالی که از bfcache استفاده می‌کند، بسیار سریع‌تر از مثالی است که از آن استفاده نمی‌کند.

bfcache نه تنها سرعت پیمایش را افزایش می‌دهد، بلکه استفاده از داده را نیز کاهش می‌دهد، زیرا منابع نیازی به دانلود مجدد ندارند.

داده‌های استفاده از کروم نشان می‌دهد که از هر ۱۰ پیمایش در دسکتاپ، ۱ پیمایش و در موبایل، ۱ پیمایش یا به عقب یا به جلو است. با فعال بودن bfcache، مرورگرها می‌توانند انتقال داده‌ها و زمان صرف شده برای بارگذاری میلیاردها صفحه وب را در هر روز از بین ببرند!

نحوه کار «حافظه پنهان»

«حافظه پنهان» مورد استفاده توسط bfcache با حافظه پنهان HTTP متفاوت است، که نقش خود را در سرعت بخشیدن به پیمایش‌های تکراری ایفا می‌کند. bfcache یک تصویر لحظه‌ای از کل صفحه در حافظه، از جمله پشته جاوا اسکریپت است، در حالی که حافظه پنهان HTTP فقط شامل پاسخ‌های درخواست‌های قبلی است. از آنجایی که بسیار نادر است که تمام درخواست‌های مورد نیاز برای بارگذاری یک صفحه از حافظه پنهان HTTP انجام شوند، بازدیدهای مکرر با استفاده از بازیابی‌های bfcache همیشه سریع‌تر از حتی بهینه‌ترین پیمایش‌های غیر bfcache هستند.

فریز کردن یک صفحه برای فعال‌سازی مجدد آن در آینده، از نظر بهترین روش برای حفظ کد در حال انجام، پیچیدگی‌هایی را به همراه دارد. برای مثال، چگونه فراخوانی‌های setTimeout() را در جایی که timeout به پایان می‌رسد در حالی که صفحه در bfcache است، مدیریت می‌کنید؟

پاسخ این است که مرورگرها هرگونه تایمر در حال انتظار یا promise های حل نشده برای صفحات موجود در bfcache، از جمله تقریباً تمام وظایف در حال انتظار در صف وظایف جاوا اسکریپت را متوقف می‌کنند و در صورت بازیابی صفحه از bfcache، پردازش وظایف را از سر می‌گیرند.

در برخی موارد، مانند زمان‌های انتظار و وعده‌ها، این ریسک نسبتاً پایینی دارد، اما در موارد دیگر می‌تواند منجر به رفتار گیج‌کننده یا غیرمنتظره شود. به عنوان مثال، اگر مرورگر وظیفه‌ای را که به عنوان بخشی از یک تراکنش IndexedDB مورد نیاز است، متوقف کند، می‌تواند بر سایر تب‌های باز در همان مبدا تأثیر بگذارد، زیرا چندین تب می‌توانند به طور همزمان به همان پایگاه‌های داده IndexedDB دسترسی داشته باشند. در نتیجه، مرورگرها معمولاً سعی نمی‌کنند صفحات را در وسط یک تراکنش IndexedDB یا هنگام استفاده از APIهایی که ممکن است بر صفحات دیگر تأثیر بگذارند، ذخیره کنند.

برای جزئیات بیشتر در مورد چگونگی تأثیر استفاده از APIهای مختلف بر واجد شرایط بودن یک صفحه برای bfcache، به بخش «صفحات خود را برای bfcache بهینه کنید» مراجعه کنید.

bfcache و iframe ها

اگر صفحه‌ای حاوی iframe های جاسازی‌شده باشد، خود iframe ها به طور جداگانه واجد شرایط bfcache نیستند. برای مثال، اگر در یک iframe به URL دیگری بروید، محتوای قبلی وارد bfcache نمی‌شود و اگر به عقب برگردید، مرورگر به جای فریم اصلی، در iframe به عقب برمی‌گردد، اما پیمایش به عقب در iframe از bfcache استفاده نمی‌کند.

با این حال، هنگامی که فریم اصلی از bfcache بازیابی می‌شود، iframe های جاسازی شده به همان شکلی که هنگام ورود صفحه به bfcache بودند، بازیابی می‌شوند.

اگر یک iframe تعبیه‌شده از APIهایی استفاده کند که این قابلیت را مسدود می‌کنند، می‌توان دسترسی فریم اصلی به bfcache را نیز مسدود کرد. برای جلوگیری از این امر می‌توان از سیاست مجوزها (Permissions Policy) تنظیم‌شده روی فریم اصلی یا استفاده از ویژگی‌های sandbox استفاده کرد.

bfcache و برنامه‌های تک صفحه‌ای (SPA)

از آنجایی که bfcache با ناوبری‌های مدیریت‌شده توسط مرورگر کار می‌کند، برای «ناوبری‌های نرم» در یک برنامه تک‌صفحه‌ای (SPA) کار نمی‌کند. با این حال، bfcache همچنان می‌تواند هنگام بازگشت به یک SPA به جای راه‌اندازی مجدد کامل آن برنامه از ابتدا، مفید باشد.

APIهایی برای مشاهده‌ی bfcache

اگرچه bfcache بهینه‌سازی‌ای است که مرورگرها به طور خودکار انجام می‌دهند، اما هنوز هم برای توسعه‌دهندگان مهم است که بدانند چه زمانی این اتفاق می‌افتد تا بتوانند صفحات خود را برای آن بهینه کنند و هرگونه معیار یا اندازه‌گیری عملکرد را بر اساس آن تنظیم کنند .

رویدادهای اصلی مورد استفاده برای مشاهده bfcache، رویدادهای انتقال صفحه pageshow و pagehide هستند که توسط اکثر مرورگرها پشتیبانی می‌شوند.

رویدادهای جدیدتر چرخه عمر صفحه - freeze و resume - نیز هنگام ورود یا خروج صفحات به bfcache و همچنین در برخی موقعیت‌های دیگر، به عنوان مثال، هنگامی که یک تب پس‌زمینه برای به حداقل رساندن استفاده از CPU ثابت می‌شود، ارسال می‌شوند. این رویدادها فقط در مرورگرهای مبتنی بر Chromium پشتیبانی می‌شوند.

مشاهده کنید که چه زمانی یک صفحه از bfcache بازیابی می‌شود

رویداد pageshow درست پس از رویداد load ، زمانی که صفحه برای اولین بار بارگذاری می‌شود و هر زمان که صفحه از bfcache بازیابی شود، اجرا می‌شود. رویداد pageshow دارای یک ویژگی persisted است که اگر صفحه از bfcache بازیابی شده باشد، مقدار آن true و در غیر این صورت false است. می‌توانید از ویژگی persisted برای تشخیص بارگذاری‌های منظم صفحه از بازیابی‌های bfcache استفاده کنید. برای مثال:

window.addEventListener('pageshow', (event) => {
  if (event.persisted) {
    console.log('This page was restored from the bfcache.');
  } else {
    console.log('This page was loaded normally.');
  }
});

در مرورگرهایی که از API چرخه عمر صفحه پشتیبانی می‌کنند، رویداد resume زمانی فعال می‌شود که صفحات از bfcache بازیابی شوند (بلافاصله قبل از رویداد pageshow ) و زمانی که کاربر دوباره از یک تب پس‌زمینه‌ی مسدود شده بازدید می‌کند. اگر می‌خواهید وضعیت یک صفحه را پس از مسدود شدن به‌روزرسانی کنید (که شامل صفحات موجود در bfcache نیز می‌شود)، می‌توانید از رویداد resume استفاده کنید، اما اگر می‌خواهید میزان بازدید سایت خود از bfcache را اندازه‌گیری کنید، باید از رویداد pageshow استفاده کنید. در برخی موارد، ممکن است لازم باشد از هر دو استفاده کنید.

برای جزئیات بیشتر در مورد بهترین شیوه‌های اندازه‌گیری bfcache، به «چگونه bfcache بر تجزیه و تحلیل و اندازه‌گیری عملکرد تأثیر می‌گذارد» مراجعه کنید.

مشاهده‌ی زمان ورود یک صفحه به bfcache

رویداد pagehide یا زمانی که یک صفحه خالی می‌شود یا زمانی که مرورگر سعی می‌کند آن را در bfcache قرار دهد، فعال می‌شود.

رویداد pagehide همچنین دارای یک ویژگی persisted است. اگر مقدار آن false ، می‌توانید مطمئن باشید که آن صفحه قرار نیست وارد bfcache شود. با این حال، اگر persisted true باشد، تضمین نمی‌کند که یک صفحه cache شود. این بدان معناست که مرورگر قصد دارد صفحه را cache کند، اما ممکن است عوامل دیگری وجود داشته باشد که cache کردن آن را غیرممکن کند.

window.addEventListener('pagehide', (event) => {
  if (event.persisted) {
    console.log('This page *might* be entering the bfcache.');
  } else {
    console.log('This page will unload normally and be discarded.');
  }
});

به طور مشابه، رویداد freeze بلافاصله پس از رویداد pagehide در صورتی که persisted برابر با true باشد، اجرا می‌شود، اما این فقط به این معنی است که مرورگر قصد دارد صفحه را ذخیره کند. ممکن است به دلایلی که بعداً توضیح داده خواهد شد، همچنان مجبور به حذف آن باشد.

صفحات خود را برای bfcache بهینه کنید

همه صفحات در bfcache ذخیره نمی‌شوند، و حتی وقتی صفحه‌ای در آنجا ذخیره می‌شود، به طور نامحدود در آنجا باقی نمی‌ماند. بسیار مهم است که توسعه‌دهندگان بدانند چه چیزی صفحات را برای bfcache واجد شرایط (و غیر واجد شرایط) می‌کند تا نرخ موفقیت حافظه پنهان خود را به حداکثر برسانند.

بخش‌های زیر بهترین شیوه‌ها را برای افزایش احتمال ذخیره صفحات شما در حافظه پنهان مرورگر ارائه می‌دهند.

هرگز از رویداد unload استفاده نکنید

مهم‌ترین راه برای بهینه‌سازی bfcache در همه مرورگرها این است که هرگز از رویداد unload استفاده نکنید. هرگز!

رویداد unload برای مرورگرها مشکل‌ساز است زیرا قبل از bfcache وجود داشته و بسیاری از صفحات در اینترنت با این فرض (معقول) کار می‌کنند که یک صفحه پس از اجرای رویداد unload دیگر وجود نخواهد داشت. این یک چالش است زیرا بسیاری از آن صفحات نیز با این فرض ساخته شده‌اند که رویداد unload هر زمان که کاربر در حال حرکت به خارج از صفحه باشد، اجرا می‌شود، که دیگر درست نیست (و مدت زیادی است که درست نبوده است ).

بنابراین مرورگرها با یک دوراهی روبرو هستند، آنها باید بین چیزی که می‌تواند تجربه کاربری را بهبود بخشد - اما ممکن است خطر خرابی صفحه را نیز به همراه داشته باشد - یکی را انتخاب کنند.

در دسکتاپ، کروم و فایرفاکس تصمیم گرفته‌اند صفحاتی را که یک شنونده‌ی unload اضافه می‌کنند، برای bfcache غیرقابل استفاده کنند، که ریسک کمتری دارد اما بسیاری از صفحات را نیز رد صلاحیت می‌کند. سافاری سعی می‌کند برخی صفحات را با یک شنونده‌ی رویداد unload ذخیره کند، اما برای کاهش احتمال خرابی، رویداد unload را هنگام حرکت کاربر اجرا نمی‌کند، که این امر باعث می‌شود این رویداد بسیار غیرقابل اعتماد باشد.

در موبایل، کروم و سافاری سعی می‌کنند صفحات را با یک شنونده رویداد unload ذخیره کنند، زیرا خطر خرابی به دلیل این واقعیت که رویداد unload همیشه در موبایل بسیار غیرقابل اعتماد بوده است، کمتر است. فایرفاکس صفحاتی را که unload استفاده می‌کنند، برای bfcache نامناسب می‌داند، به جز در iOS، که همه مرورگرها از موتور رندر WebKit استفاده می‌کنند و بنابراین مانند سافاری رفتار می‌کنند.

به جای استفاده از رویداد unload ، از رویداد pagehide استفاده کنید. رویداد pagehide در تمام مواردی که رویداد unload اجرا می‌شود، و همچنین هنگامی که صفحه‌ای در bfcache قرار می‌گیرد، اجرا می‌شود.

در واقع، Lighthouse یک حسابرسی no-unload-listeners دارد که در صورت وجود هرگونه جاوا اسکریپت در صفحاتشان (از جمله کتابخانه‌های شخص ثالث) که یک شنونده رویداد unload اضافه می‌کند، به توسعه‌دهندگان هشدار می‌دهد.

به دلیل غیرقابل اعتماد بودن و تأثیر آن بر عملکرد bfcache، کروم در حال بررسی منسوخ کردن رویداد unload است.

از سیاست مجوز برای جلوگیری از استفاده از unload handlerها در یک صفحه استفاده کنید.

سایت‌هایی که unload event handler استفاده نمی‌کنند، می‌توانند با استفاده از Permissions Policy از عدم اضافه شدن این موارد اطمینان حاصل کنند.

Permissions-Policy: unload=()

این همچنین مانع از آن می‌شود که اشخاص ثالث یا افزونه‌ها با اضافه کردن unload handlers و غیرقابل قبول کردن سایت برای bfcache، سرعت سایت را کاهش دهند.

فقط شنونده‌های beforeunload را به صورت مشروط اضافه کنید

رویداد beforeunload صفحات شما را در مرورگرهای مدرن از bfcache محروم نمی‌کند، اما قبلاً این کار را می‌کرد و هنوز هم غیرقابل اعتماد است، بنابراین از استفاده از آن خودداری کنید، مگر اینکه کاملاً ضروری باشد.

با این حال، برخلاف رویداد unload ، کاربردهای مشروعی برای beforeunload وجود دارد. به عنوان مثال، وقتی می‌خواهید به کاربر هشدار دهید که تغییرات ذخیره نشده‌ای دارد که در صورت ترک صفحه، آنها را از دست خواهد داد. در این حالت، توصیه می‌شود فقط زمانی که کاربر تغییرات ذخیره نشده‌ای دارد، شنونده‌های beforeunload را اضافه کنید و بلافاصله پس از ذخیره تغییرات ذخیره نشده، آنها را حذف کنید.

نکن
window.addEventListener('beforeunload', (event) => {
  if (pageHasUnsavedChanges()) {
    event.preventDefault();
    return event.returnValue = 'Are you sure you want to exit?';
  }
});
این کد یک شنونده beforeunload را بدون قید و شرط اضافه می‌کند.
انجام دهید
function beforeUnloadListener(event) {
  event.preventDefault();
  return event.returnValue = 'Are you sure you want to exit?';
};

// A function that invokes a callback when the page has unsaved changes.
onPageHasUnsavedChanges(() => {
  window.addEventListener('beforeunload', beforeUnloadListener);
});

// A function that invokes a callback when the page's unsaved changes are resolved.
onAllChangesSaved(() => {
  window.removeEventListener('beforeunload', beforeUnloadListener);
});
این کد فقط زمانی که به شنونده beforeunload نیاز باشد آن را اضافه می‌کند (و زمانی که نیازی به آن نباشد آن را حذف می‌کند).

استفاده از Cache-Control: no-store

Cache-Control: no-store یک هدر HTTP است که سرورهای وب می‌توانند پاسخ‌هایی را تنظیم کنند که به مرورگر دستور می‌دهد پاسخ را در هیچ حافظه پنهان HTTP ذخیره نکند. این برای منابعی که حاوی اطلاعات حساس کاربر هستند، مانند صفحات پشت صفحه ورود، استفاده می‌شود.

اگرچه bfcache یک حافظه پنهان HTTP نیست، اما از نظر تاریخی، وقتی Cache-Control: no-store روی خود منبع صفحه تنظیم می‌شود (برخلاف هر زیرمنبعی)، مرورگرها تصمیم گرفته‌اند که صفحه را در bfcache ذخیره نکنند، بنابراین هر صفحه‌ای که از Cache-Control: no-store استفاده می‌کند، ممکن است واجد شرایط bfcache نباشد. کارهایی در حال انجام است تا این رفتار برای کروم به شیوه‌ای حفظ حریم خصوصی تغییر کند .

از آنجایی که Cache-Control: no-store واجد شرایط بودن یک صفحه برای bfcache را محدود می‌کند، فقط باید روی صفحاتی تنظیم شود که حاوی اطلاعات حساس هستند و هیچ نوع ذخیره‌سازی در حافظه پنهان (caching) در آنها هرگز مناسب نیست.

برای صفحاتی که باید همیشه محتوای به‌روز ارائه دهند - و آن محتوا حاوی اطلاعات حساس نیست - Cache-Control: no-cache یا Cache-Control: max-age=0 استفاده کنید. این دستورالعمل‌ها به مرورگر دستور می‌دهند که قبل از ارائه محتوا، آن را مجدداً اعتبارسنجی کند و بر واجد شرایط بودن bfcache یک صفحه تأثیری ندارند.

توجه داشته باشید که وقتی صفحه‌ای از bfcache بازیابی می‌شود، از حافظه بازیابی می‌شود، نه از حافظه پنهان HTTP. در نتیجه، دستورالعمل‌هایی مانند Cache-Control: no-cache یا Cache-Control: max-age=0 در نظر گرفته نمی‌شوند و قبل از نمایش محتوا به کاربر، هیچ اعتبارسنجی مجددی انجام نمی‌شود.

با این حال، این احتمالاً هنوز هم تجربه کاربری بهتری خواهد داشت، زیرا بازیابی‌های bfcache فوری هستند و - از آنجایی که صفحات برای مدت طولانی در bfcache نمی‌مانند - بعید است که محتوا قدیمی باشد. با این حال، اگر محتوای شما دقیقه به دقیقه تغییر می‌کند، می‌توانید با استفاده از رویداد pageshow ، همانطور که در بخش بعدی توضیح داده شده است، هرگونه به‌روزرسانی را دریافت کنید.

به‌روزرسانی داده‌های قدیمی یا حساس پس از بازیابی bfcache

اگر سایت شما وضعیت کاربر - به خصوص هرگونه اطلاعات حساس کاربر - را نگه می‌دارد، پس از بازیابی صفحه از bfcache، این داده‌ها باید به‌روزرسانی یا پاک شوند.

برای مثال، اگر کاربری به صفحه پرداخت برود و سپس سبد خرید خود را به‌روزرسانی کند، اگر یک صفحه قدیمی از bfcache بازیابی شود، پیمایش به عقب می‌تواند به‌طور بالقوه اطلاعات قدیمی را افشا کند.

مثال مهم‌تر دیگر این است که اگر کاربری از یک سایت در یک رایانه عمومی خارج شود و کاربر بعدی روی دکمه برگشت کلیک کند، این می‌تواند به‌طور بالقوه داده‌های خصوصی را که کاربر فرض کرده هنگام خروج پاک شده است، افشا کند.

برای جلوگیری از چنین موقعیت‌هایی، بهتر است همیشه صفحه را پس از رویداد pageshow به‌روزرسانی کنید، البته اگر event.persisted true باشد:

window.addEventListener('pageshow', (event) => {
  if (event.persisted) {
    // Do any checks and updates to the page
  }
});

اگرچه در حالت ایده‌آل، شما باید محتوا را در جای خود به‌روزرسانی کنید، اما برای برخی تغییرات ممکن است بخواهید صفحه را به‌طور کامل بارگذاری مجدد کنید. کد زیر وجود یک کوکی مخصوص سایت را در رویداد pageshow بررسی می‌کند و در صورت عدم یافتن کوکی، صفحه را مجدداً بارگذاری می‌کند:

window.addEventListener('pageshow', (event) => {
  if (event.persisted && !document.cookie.match(/my-cookie)) {
    // Force a reload if the user has logged out.
    location.reload();
  }
});

بارگذاری مجدد این مزیت را دارد که همچنان تاریخچه را حفظ می‌کند (تا امکان پیمایش به جلو فراهم شود)، اما در برخی موارد، تغییر مسیر ممکن است مناسب‌تر باشد.

بازیابی تبلیغات و bfcache

ممکن است وسوسه‌انگیز باشد که سعی کنید از استفاده از bfcache برای نمایش مجموعه‌ای جدید از تبلیغات در هر پیمایش به عقب/جلو خودداری کنید. با این حال، علاوه بر تأثیر بر عملکرد، جای سوال است که آیا چنین رفتاری منجر به تعامل بهتر با تبلیغات می‌شود یا خیر. کاربران ممکن است متوجه تبلیغی شده باشند که قصد بازگشت به آن و کلیک کردن روی آن را داشته باشند، اما با بارگذاری مجدد به جای بازیابی از bfcache، قادر به انجام این کار نباشند. آزمایش این سناریو - در حالت ایده‌آل با یک تست A/B - قبل از هرگونه فرضیه‌سازی مهم است.

برای سایت‌هایی که می‌خواهند تبلیغات را در bfcache restore به‌روزرسانی کنند، به‌روزرسانی فقط تبلیغات در رویداد pageshow زمانی که event.persisted روی true تنظیم شده باشد، اجازه می‌دهد این اتفاق بدون تأثیر بر عملکرد صفحه رخ دهد. با ارائه‌دهنده تبلیغات خود مشورت کنید، اما در اینجا یک مثال در مورد نحوه انجام این کار با Google Publishing Tag آورده شده است .

از ارجاع window.opener خودداری کنید

در مرورگرهای قدیمی‌تر، اگر صفحه‌ای با استفاده از تابع window.open() از لینکی با target=_blank و بدون مشخص کردن rel="noopener" باز می‌شد، صفحه‌ی باز شده دارای ارجاعی به شیء window صفحه‌ی باز شده می‌بود.

علاوه بر اینکه یک ریسک امنیتی است ، صفحه‌ای با ارجاع غیر تهی window.opener را نمی‌توان با خیال راحت در bfcache قرار داد، زیرا این کار می‌تواند هر صفحه‌ای را که سعی در دسترسی به آن دارد، از کار بیندازد.

در نتیجه، بهتر است از ایجاد ارجاعات window.opener خودداری کنید. می‌توانید این کار را با استفاده از rel="noopener" در هر زمان ممکن انجام دهید (توجه داشته باشید، این اکنون به طور پیش‌فرض در همه مرورگرهای مدرن است). اگر سایت شما نیاز به باز کردن یک پنجره و کنترل آن از طریق window.postMessage() یا ارجاع مستقیم به شیء window دارد، نه پنجره باز شده و نه بازکننده واجد شرایط دریافت bfcache نخواهند بود.

قبل از اینکه کاربر از صفحه خارج شود، اتصالات باز را ببندید

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

اگر این وظایف زمان‌بندی‌شده‌ی جاوااسکریپت فقط به APIهای DOM یا سایر APIهای مجزا از صفحه‌ی فعلی دسترسی داشته باشند، متوقف کردن این وظایف در حالی که صفحه برای کاربر قابل مشاهده نیست، مشکلی ایجاد نمی‌کند.

با این حال، اگر این وظایف به APIهایی متصل باشند که از صفحات دیگر در همان مبدا نیز قابل دسترسی هستند (برای مثال: IndexedDB، Web Locks، WebSockets)، این می‌تواند مشکل‌ساز باشد زیرا متوقف کردن این وظایف ممکن است از اجرای کد در تب‌های دیگر جلوگیری کند.

در نتیجه، برخی از مرورگرها در سناریوهای زیر سعی در قرار دادن صفحه در bfcache نمی‌کنند:

  • صفحاتی با اتصال باز IndexedDB .
  • صفحاتی با fetch() یا XMLHttpRequest در حال انجام.
  • صفحاتی با اتصال WebSocket یا WebRTC باز. کروم (از تاریخ ۱۴۹) و سافاری هیچ مسدودیتی روی WebSocketهای باز انجام نمی‌دهند، اما سایر مرورگرها این کار را انجام می‌دهند.

اگر صفحه شما از هر یک از این APIها استفاده می‌کند، اکیداً توصیه می‌کنیم در طول رویداد pagehide یا freeze ، اتصالات را ببندید و مشاهده‌گرها را حذف یا قطع کنید. این کار به مرورگر اجازه می‌دهد تا بدون خطر تأثیرگذاری بر سایر تب‌های باز، صفحه را با خیال راحت ذخیره کند.

سپس، اگر صفحه از bfcache بازیابی شود، می‌توانید در طول رویداد pageshow یا resume دوباره آن APIها را باز کنید یا دوباره به آنها متصل شوید، یا اینکه آنها را همیشه به طور خودکار با استفاده از رویدادهای error یا close دوباره باز کنید. در صورت استفاده از چندین رویداد، مطمئن شوید که چندین اتصال را باز نمی‌کنید.

مثال زیر نشان می‌دهد که چگونه می‌توان با بستن یک اتصال باز در شنونده رویداد pagehide از واجد شرایط بودن صفحاتی که از IndexedDB استفاده می‌کنند برای bfcache اطمینان حاصل کرد:

let dbPromise;
function openDB() {
  if (!dbPromise) {
    dbPromise = new Promise((resolve, reject) => {
      const req = indexedDB.open('my-db', 1);
      req.onupgradeneeded = () => req.result.createObjectStore('keyval');
      req.onerror = () => reject(req.error);
      req.onsuccess = () => resolve(req.result);
    });
  }
  return dbPromise;
}

// Close the connection to the database when the user leaves.
window.addEventListener('pagehide', () => {
  if (dbPromise) {
    dbPromise.then(db => db.close());
    dbPromise = null;
  }
});

// Open the connection when the page is loaded or restored from bfcache.
window.addEventListener('pageshow', () => openDB());

آزمایش کنید تا مطمئن شوید صفحات شما قابل ذخیره سازی هستند

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

برای آزمایش یک صفحه:

  1. در کروم به صفحه مورد نظر بروید.
  2. در DevTools، به Application -> Back-forward Cache بروید.
  3. روی دکمه‌ی اجرای تست کلیک کنید. سپس DevTools سعی می‌کند به عقب و جلو حرکت کند تا مشخص کند که آیا صفحه می‌تواند از bfcache بازیابی شود یا خیر.
پنل حافظه پنهان رو به جلو در DevTools
پنل Back-forward Cache در DevTools.

اگر آزمایش موفقیت‌آمیز باشد، پنل عبارت «بازیابی از حافظه پنهان رو به جلو» را گزارش می‌دهد.

DevTools گزارش می‌دهد که یک صفحه با موفقیت از bfcache بازیابی شده است
صفحه با موفقیت بازیابی شد.

اگر ناموفق باشد، پنل دلیل آن را نشان می‌دهد. اگر دلیل چیزی باشد که شما به عنوان یک توسعه‌دهنده می‌توانید به آن رسیدگی کنید، پنل آن را به عنوان «قابل اقدام» علامت‌گذاری می‌کند.

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

در این مثال، استفاده از شنونده رویداد unload باعث می‌شود صفحه برای bfcache واجد شرایط نباشد . می‌توانید با تغییر از unload به استفاده از pagehide این مشکل را برطرف کنید:

انجام دهید
window.addEventListener('pagehide', ...);
نکن
window.addEventListener('unload', ...);

Lighthouse 10.0 همچنین یک bfcache audit اضافه کرده است که آزمایش مشابهی را انجام می‌دهد. برای اطلاعات بیشتر، به مستندات bfcache audit مراجعه کنید.

چگونه bfcache بر تجزیه و تحلیل و اندازه‌گیری عملکرد تأثیر می‌گذارد

اگر از یک ابزار تحلیلی برای اندازه‌گیری بازدیدهای سایت خود استفاده می‌کنید، ممکن است متوجه کاهش تعداد کل بازدیدهای صفحه گزارش شده شوید، زیرا کروم bfcache را برای کاربران بیشتری فعال می‌کند.

در واقع، شما احتمالاً همین الان هم تعداد بازدیدهای صفحه را از مرورگرهای دیگری که bfcache را پیاده‌سازی کرده‌اند، کمتر از مقدار واقعی گزارش می‌کنید، زیرا بسیاری از کتابخانه‌های تحلیلی محبوب، بازیابی‌های bfcache را به عنوان بازدیدهای جدید صفحه اندازه‌گیری نمی‌کنند.

برای اینکه بازیابی‌های bfcache در تعداد بازدیدهای صفحه شما لحاظ شوند، برای رویداد pageshow شنونده تنظیم کنید و ویژگی persisted را بررسی کنید.

مثال زیر نحوه انجام این کار را با گوگل آنالیتیکس نشان می‌دهد. سایر ابزارهای تحلیلی احتمالاً از منطق مشابهی استفاده می‌کنند:

// Send a pageview when the page is first loaded.
// This happens by default just by loading gtag
gtag('config', 'TAG_ID');

window.addEventListener('pageshow', (event) => {
  // Send another pageview if the page is restored from bfcache.
  if (event.persisted) {
    gtag('event', 'page_view');
  }
});

نسبت موفقیت bfcache خود را اندازه‌گیری کنید

همچنین می‌توانید بررسی کنید که آیا از bfcache استفاده شده است یا خیر، تا صفحاتی که از bfcache استفاده نمی‌کنند را شناسایی کنید. این کار را می‌توان با اندازه‌گیری نوع ناوبری برای بارگذاری صفحات انجام داد:

// Send a navigation_type when the page is first loaded.
// To do this disable the default pageview so you can manually send it
// supplemented with the additional detail.
gtag('config', 'TAG_ID', { send_page_view: false });
gtag('event', 'page_view', {
   'navigation_type': performance.getEntriesByType('navigation')[0].type;
});

window.addEventListener('pageshow', (event) => {
  if (event.persisted) {
    // Send another pageview if the page is restored from bfcache.
    gtag('event', 'page_view', {
      'navigation_type': 'back_forward_cache';
    });
  }
});

با استفاده از شمارش‌های مربوط به پیمایش‌های back_forward و back_forward_cache ، نسبت موفقیت bfcache خود را محاسبه کنید.

مهم است بدانید که تعدادی سناریو، خارج از کنترل صاحبان سایت، وجود دارد که در آن‌ها ناوبری Back/Forward از bfcache استفاده نمی‌کند، از جمله:

  • وقتی کاربر از مرورگر خارج می‌شود و دوباره آن را اجرا می‌کند
  • وقتی کاربر یک تب را کپی می‌کند
  • وقتی کاربر یک تب را می‌بندد و دوباره باز می‌کند

در برخی از این موارد، نوع ناوبری اصلی ممکن است توسط برخی مرورگرها حفظ شود و بنابراین ممکن است نوعی از back_forward را نشان دهد، علیرغم اینکه این ناوبری‌ها Back/Forward نیستند.

حتی بدون این استثنائات، bfcache پس از مدتی برای صرفه‌جویی در حافظه حذف خواهد شد.

بنابراین، صاحبان وب‌سایت نباید انتظار داشته باشند که نسبت موفقیت bfcache برای همه ناوبری‌های back_forward صد در صد باشد. با این حال، اندازه‌گیری این نسبت می‌تواند برای شناسایی صفحاتی که خود صفحه مانع از استفاده از bfcache برای بخش زیادی از ناوبری‌های back و forward می‌شود، مفید باشد.

تیم کروم API مربوط به NotRestoredReasons را اضافه کرده است تا به آشکار شدن دلایل عدم استفاده صفحات از bfcache کمک کند، بنابراین توسعه‌دهندگان می‌توانند نرخ موفقیت bfcache خود را بهبود بخشند. تیم کروم همچنین انواع ناوبری را به CrUX اضافه کرده است که امکان مشاهده تعداد ناوبری‌های bfcache را حتی بدون اندازه‌گیری شخصی آن فراهم می‌کند.

اندازه‌گیری عملکرد

bfcache همچنین می‌تواند بر معیارهای عملکرد جمع‌آوری‌شده در این زمینه ، به‌ویژه معیارهایی که زمان بارگذاری صفحه را اندازه‌گیری می‌کنند، تأثیر منفی بگذارد.

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

نتیجه، کاهش سرعت بارگذاری صفحات در مجموعه داده‌های شما است که احتمالاً توزیع را کندتر می‌کند - با وجود این واقعیت که عملکرد تجربه شده توسط کاربر احتمالاً بهبود یافته است!

چند راه برای مقابله با این مشکل وجود دارد. یکی از آنها حاشیه‌نویسی تمام معیارهای بارگذاری صفحه با نوع ناوبری مربوطه‌شان است: navigate ، reload ، back_forward یا prerender . این به شما امکان می‌دهد عملکرد خود را در این نوع ناوبری‌ها، حتی اگر توزیع کلی منفی باشد، همچنان رصد کنید. ما این رویکرد را برای معیارهای بارگذاری صفحه غیر کاربر محور مانند زمان اولین بایت (TTFB) توصیه می‌کنیم.

برای معیارهای کاربر محور مانند Core Web Vitals ، گزینه بهتر این است که مقداری را گزارش دهید که دقیق‌تر نشان دهنده تجربه کاربر باشد.

تأثیر بر ویتامین‌های اصلی وب

Core Web Vitals تجربه کاربر از یک صفحه وب را در ابعاد مختلف (سرعت بارگذاری، تعامل، پایداری بصری) اندازه‌گیری می‌کند و از آنجایی که کاربران بازیابی bfcache را به عنوان پیمایش سریع‌تر از بارگذاری کامل صفحه تجربه می‌کنند، مهم است که معیارهای Core Web Vitals این را منعکس کنند. از این گذشته، برای کاربر مهم نیست که bfcache فعال بوده یا خیر، او فقط مهم است که پیمایش سریع باشد!

ابزارهایی که معیارهای Core Web Vitals را جمع‌آوری و گزارش می‌دهند، مانند گزارش تجربه کاربری کروم ، بازیابی‌های bfcache را به عنوان بازدیدهای جداگانه از صفحه در مجموعه داده‌های خود در نظر می‌گیرند. و در حالی که APIهای عملکرد وب اختصاصی برای اندازه‌گیری این معیارها پس از بازیابی‌های bfcache وجود ندارد، می‌توانید مقادیر آنها را با استفاده از APIهای وب موجود تخمین بزنید:

  • برای Largest Contentful Paint (LCP) ، از دلتای بین برچسب زمانی رویداد pageshow و برچسب زمانی فریم نقاشی شده بعدی استفاده کنید، زیرا تمام عناصر موجود در فریم به طور همزمان نقاشی می‌شوند. در مورد بازیابی bfcache، LCP و FCP یکسان هستند.
  • برای تعامل با رنگ بعدی (INP) ، به استفاده از Performance Observer فعلی خود ادامه دهید، اما مقدار INP فعلی را به ۰ تنظیم مجدد کنید.
  • برای تغییر چیدمان تجمعی (CLS) ، به استفاده از Performance Observer فعلی خود ادامه دهید، اما مقدار CLS فعلی را به ۰ تنظیم مجدد کنید.

برای جزئیات بیشتر در مورد چگونگی تأثیر bfcache بر هر معیار، به صفحات راهنمای معیارهای Core Web Vitals مراجعه کنید. برای مثال خاصی از نحوه پیاده‌سازی نسخه‌های bfcache از این معیارها، به PR اضافه کردن آنها به کتابخانه JS web-vitals مراجعه کنید.

کتابخانه جاوا اسکریپت web-vitals در معیارهایی که گزارش می‌دهد، از بازیابی‌های bfcache پشتیبانی می‌کند .

منابع اضافی

،

منتشر شده: ۲۵ مه ۲۰۲۳، آخرین به‌روزرسانی: ۲ ژوئیه ۲۰۲۶

حافظه پنهان Back/Forward (یا bfcache) یک بهینه‌سازی مرورگر است که امکان پیمایش سریع به عقب و جلو را فراهم می‌کند. این قابلیت به طور قابل توجهی تجربه مرور وب را بهبود می‌بخشد، به خصوص برای کاربرانی که شبکه‌ها یا دستگاه‌های کندتری دارند.

به عنوان توسعه‌دهندگان وب، بسیار مهم است که بدانید چگونه صفحات خود را برای bfcache بهینه کنید تا کاربران شما بتوانند از مزایای آن بهره‌مند شوند.

سازگاری با مرورگرها

همه مرورگرهای اصلی شامل bfcache هستند، از جمله کروم از نسخه ۹۶ به بعد، فایرفاکس و سافاری .

اصول اولیه bfcache

با استفاده از حافظه پنهان back/forward (bfcache)، به جای اینکه هنگام خروج کاربر از صفحه، آن را از بین ببریم، تخریب را به تعویق می‌اندازیم و اجرای JS را متوقف می‌کنیم. اگر کاربر به زودی به عقب برگردد، صفحه را دوباره قابل مشاهده می‌کنیم و اجرای JS را از حالت مکث خارج می‌کنیم. این منجر به پیمایش تقریباً فوری صفحه برای کاربر می‌شود.

چند بار شده که از یک وب‌سایت بازدید کرده‌اید و روی لینکی کلیک کرده‌اید تا به صفحه دیگری بروید، اما متوجه شده‌اید که این صفحه آن چیزی نیست که می‌خواستید و دکمه بازگشت را زده‌اید؟ در آن لحظه، bfcache می‌تواند تفاوت زیادی در سرعت بارگذاری صفحه قبلی ایجاد کند:

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

برای درک سرعتی که bfcache می‌تواند برای ناوبری به ارمغان بیاورد، این ویدیو از نحوه‌ی عملکرد آن را تماشا کنید:

استفاده از bfcache باعث می‌شود صفحات در هنگام پیمایش به عقب و جلو بسیار سریع‌تر بارگذاری شوند.

در ویدیو، مثالی که از bfcache استفاده می‌کند، بسیار سریع‌تر از مثالی است که از آن استفاده نمی‌کند.

bfcache نه تنها سرعت پیمایش را افزایش می‌دهد، بلکه استفاده از داده را نیز کاهش می‌دهد، زیرا منابع نیازی به دانلود مجدد ندارند.

داده‌های استفاده از کروم نشان می‌دهد که از هر ۱۰ پیمایش در دسکتاپ، ۱ پیمایش و در موبایل، ۱ پیمایش یا به عقب یا به جلو است. با فعال بودن bfcache، مرورگرها می‌توانند انتقال داده‌ها و زمان صرف شده برای بارگذاری میلیاردها صفحه وب را در هر روز از بین ببرند!

نحوه کار «حافظه پنهان»

«حافظه پنهان» مورد استفاده توسط bfcache با حافظه پنهان HTTP متفاوت است، که نقش خود را در سرعت بخشیدن به پیمایش‌های تکراری ایفا می‌کند. bfcache یک تصویر لحظه‌ای از کل صفحه در حافظه، از جمله پشته جاوا اسکریپت است، در حالی که حافظه پنهان HTTP فقط شامل پاسخ‌های درخواست‌های قبلی است. از آنجایی که بسیار نادر است که تمام درخواست‌های مورد نیاز برای بارگذاری یک صفحه از حافظه پنهان HTTP انجام شوند، بازدیدهای مکرر با استفاده از بازیابی‌های bfcache همیشه سریع‌تر از حتی بهینه‌ترین پیمایش‌های غیر bfcache هستند.

فریز کردن یک صفحه برای فعال‌سازی مجدد آن در آینده، از نظر بهترین روش برای حفظ کد در حال انجام، پیچیدگی‌هایی را به همراه دارد. برای مثال، چگونه فراخوانی‌های setTimeout() را در جایی که timeout به پایان می‌رسد در حالی که صفحه در bfcache است، مدیریت می‌کنید؟

پاسخ این است که مرورگرها هرگونه تایمر در حال انتظار یا promise های حل نشده برای صفحات موجود در bfcache، از جمله تقریباً تمام وظایف در حال انتظار در صف وظایف جاوا اسکریپت را متوقف می‌کنند و در صورت بازیابی صفحه از bfcache، پردازش وظایف را از سر می‌گیرند.

در برخی موارد، مانند زمان‌های انتظار و وعده‌ها، این ریسک نسبتاً پایینی دارد، اما در موارد دیگر می‌تواند منجر به رفتار گیج‌کننده یا غیرمنتظره شود. به عنوان مثال، اگر مرورگر وظیفه‌ای را که به عنوان بخشی از یک تراکنش IndexedDB مورد نیاز است، متوقف کند، می‌تواند بر سایر تب‌های باز در همان مبدا تأثیر بگذارد، زیرا چندین تب می‌توانند به طور همزمان به همان پایگاه‌های داده IndexedDB دسترسی داشته باشند. در نتیجه، مرورگرها معمولاً سعی نمی‌کنند صفحات را در وسط یک تراکنش IndexedDB یا هنگام استفاده از APIهایی که ممکن است بر صفحات دیگر تأثیر بگذارند، ذخیره کنند.

برای جزئیات بیشتر در مورد چگونگی تأثیر استفاده از APIهای مختلف بر واجد شرایط بودن یک صفحه برای bfcache، به بخش «صفحات خود را برای bfcache بهینه کنید» مراجعه کنید.

bfcache و iframe ها

اگر صفحه‌ای حاوی iframe های جاسازی‌شده باشد، خود iframe ها به طور جداگانه واجد شرایط bfcache نیستند. برای مثال، اگر در یک iframe به URL دیگری بروید، محتوای قبلی وارد bfcache نمی‌شود و اگر به عقب برگردید، مرورگر به جای فریم اصلی، در iframe به عقب برمی‌گردد، اما پیمایش به عقب در iframe از bfcache استفاده نمی‌کند.

با این حال، هنگامی که فریم اصلی از bfcache بازیابی می‌شود، iframe های جاسازی شده به همان شکلی که هنگام ورود صفحه به bfcache بودند، بازیابی می‌شوند.

اگر یک iframe تعبیه‌شده از APIهایی استفاده کند که این قابلیت را مسدود می‌کنند، می‌توان دسترسی فریم اصلی به bfcache را نیز مسدود کرد. برای جلوگیری از این امر می‌توان از سیاست مجوزها (Permissions Policy) تنظیم‌شده روی فریم اصلی یا استفاده از ویژگی‌های sandbox استفاده کرد.

bfcache و برنامه‌های تک صفحه‌ای (SPA)

از آنجایی که bfcache با ناوبری‌های مدیریت‌شده توسط مرورگر کار می‌کند، برای «ناوبری‌های نرم» در یک برنامه تک‌صفحه‌ای (SPA) کار نمی‌کند. با این حال، bfcache همچنان می‌تواند هنگام بازگشت به یک SPA به جای راه‌اندازی مجدد کامل آن برنامه از ابتدا، مفید باشد.

APIهایی برای مشاهده‌ی bfcache

اگرچه bfcache بهینه‌سازی‌ای است که مرورگرها به طور خودکار انجام می‌دهند، اما هنوز هم برای توسعه‌دهندگان مهم است که بدانند چه زمانی این اتفاق می‌افتد تا بتوانند صفحات خود را برای آن بهینه کنند و هرگونه معیار یا اندازه‌گیری عملکرد را بر اساس آن تنظیم کنند .

رویدادهای اصلی مورد استفاده برای مشاهده bfcache، رویدادهای انتقال صفحه pageshow و pagehide هستند که توسط اکثر مرورگرها پشتیبانی می‌شوند.

رویدادهای جدیدتر چرخه عمر صفحه - freeze و resume - نیز هنگام ورود یا خروج صفحات به bfcache و همچنین در برخی موقعیت‌های دیگر، به عنوان مثال، هنگامی که یک تب پس‌زمینه برای به حداقل رساندن استفاده از CPU ثابت می‌شود، ارسال می‌شوند. این رویدادها فقط در مرورگرهای مبتنی بر Chromium پشتیبانی می‌شوند.

مشاهده کنید که چه زمانی یک صفحه از bfcache بازیابی می‌شود

رویداد pageshow درست پس از رویداد load ، زمانی که صفحه برای اولین بار بارگذاری می‌شود و هر زمان که صفحه از bfcache بازیابی شود، اجرا می‌شود. رویداد pageshow دارای یک ویژگی persisted است که اگر صفحه از bfcache بازیابی شده باشد، مقدار آن true و در غیر این صورت false است. می‌توانید از ویژگی persisted برای تشخیص بارگذاری‌های منظم صفحه از بازیابی‌های bfcache استفاده کنید. برای مثال:

window.addEventListener('pageshow', (event) => {
  if (event.persisted) {
    console.log('This page was restored from the bfcache.');
  } else {
    console.log('This page was loaded normally.');
  }
});

در مرورگرهایی که از API چرخه عمر صفحه پشتیبانی می‌کنند، رویداد resume زمانی فعال می‌شود که صفحات از bfcache بازیابی شوند (بلافاصله قبل از رویداد pageshow ) و زمانی که کاربر دوباره از یک تب پس‌زمینه‌ی مسدود شده بازدید می‌کند. اگر می‌خواهید وضعیت یک صفحه را پس از مسدود شدن به‌روزرسانی کنید (که شامل صفحات موجود در bfcache نیز می‌شود)، می‌توانید از رویداد resume استفاده کنید، اما اگر می‌خواهید میزان بازدید سایت خود از bfcache را اندازه‌گیری کنید، باید از رویداد pageshow استفاده کنید. در برخی موارد، ممکن است لازم باشد از هر دو استفاده کنید.

برای جزئیات بیشتر در مورد بهترین شیوه‌های اندازه‌گیری bfcache، به «چگونه bfcache بر تجزیه و تحلیل و اندازه‌گیری عملکرد تأثیر می‌گذارد» مراجعه کنید.

مشاهده‌ی زمان ورود یک صفحه به bfcache

The pagehide event fires either when a page unloads or when the browser tries to put it in the bfcache.

The pagehide event also has a persisted property. If it's false , you can be confident a that page isn't about to enter the bfcache. However, persisted being true doesn't guarantee that a page will be cached. It means the browser intends to cache the page, but there may be other factors that make it impossible to cache.

window.addEventListener('pagehide', (event) => {
  if (event.persisted) {
    console.log('This page *might* be entering the bfcache.');
  } else {
    console.log('This page will unload normally and be discarded.');
  }
});

Similarly, the freeze event fires immediately after the pagehide event if persisted is true , but that only means the browser intends to cache the page. It might still have to discard it for a number of reasons explained later.

Optimize your pages for bfcache

Not all pages get stored in bfcache, and even when a page does get stored there, it won't stay there indefinitely. It's critical that developers understand what makes pages eligible (and ineligible) for bfcache to maximize their cache-hit rates.

The following sections outline the best practices to make it as likely as possible that the browser can cache your pages.

Never use the unload event

The most important way to optimize for bfcache in all browsers is to never use the unload event. Ever!

The unload event is problematic for browsers because it predates bfcache and many pages on the internet operate under the (reasonable) assumption that a page won't continue to exist after the unload event has fired. This presents a challenge because many of those pages were also built with the assumption that the unload event would fire any time a user is navigating away, which is no longer true (and hasn't been true for a long time ).

So browsers are faced with a dilemma, they have to choose between something that can improve the user experience—but might also risk breaking the page.

On desktop, Chrome and Firefox have chosen to make pages ineligible for bfcache if they add an unload listener, which is less risky but also disqualifies a lot of pages. Safari will attempt to cache some pages with an unload event listener, but to reduce potential breakage it won't run the unload event when a user is navigating away, which makes the event very unreliable.

On mobile, Chrome and Safari will attempt to cache pages with an unload event listener since the risk of breakage is lower due to the fact that the unload event has always been extremely unreliable on mobile. Firefox treats pages that use unload as ineligible for the bfcache, except on iOS, where all browsers use the WebKit rendering engine, and so behave like Safari.

Instead of using the unload event, use the pagehide event. The pagehide event fires in all cases where the unload event fires, and it also fires when a page is put in the bfcache.

In fact, Lighthouse has a no-unload-listeners audit , which will warn developers if any JavaScript on their pages (including that from third-party libraries) adds an unload event listener.

Due to its unreliability, and the performance impact for bfcache, Chrome is looking to deprecate the unload event .

Use Permission Policy to prevent unload handlers being used on a page

Sites that don't use unload event handlers can ensure these are not added by using a Permissions Policy .

Permissions-Policy: unload=()

This also prevents third parties or extensions from slowing the site down by adding unload handlers and making the site ineligible for the bfcache.

Only add beforeunload listeners conditionally

The beforeunload event won't make your pages ineligible for bfcache in modern browsers' bfcache but previously it did and it is still unreliable, so avoid using it unless absolutely necessary.

Unlike the unload event, however, there are legitimate uses for beforeunload . For example, when you want to warn the user that they have unsaved changes they'll lose if they leave the page. In this case, it's recommended that you only add beforeunload listeners when a user has unsaved changes and then remove them immediately after the unsaved changes are saved.

نکن
window.addEventListener('beforeunload', (event) => {
  if (pageHasUnsavedChanges()) {
    event.preventDefault();
    return event.returnValue = 'Are you sure you want to exit?';
  }
});
This code adds a beforeunload listener unconditionally.
انجام دهید
function beforeUnloadListener(event) {
  event.preventDefault();
  return event.returnValue = 'Are you sure you want to exit?';
};

// A function that invokes a callback when the page has unsaved changes.
onPageHasUnsavedChanges(() => {
  window.addEventListener('beforeunload', beforeUnloadListener);
});

// A function that invokes a callback when the page's unsaved changes are resolved.
onAllChangesSaved(() => {
  window.removeEventListener('beforeunload', beforeUnloadListener);
});
This code only adds the beforeunload listener when it's needed (and removes it when it's not).

Minimize use of Cache-Control: no-store

Cache-Control: no-store is an HTTP header web servers can set on responses that instructs the browser not to store the response in any HTTP cache. It's used for resources containing sensitive user information, such as pages behind a login.

Although bfcache is not an HTTP cache, historically, when Cache-Control: no-store is set on the page resource itself (as opposed to any subresource), browsers have chosen not to store the page in bfcache so any pages using Cache-Control: no-store may not be eligible for bfcache. There is work underway to change this behavior for Chrome in a privacy-preserving manner.

Since Cache-Control: no-store restricts a page's eligibility for bfcache, it should only be set on pages that contain sensitive information where caching of any sort is never appropriate.

For pages that need to always serve up-to-date content—and that content does not contain sensitive information—use Cache-Control: no-cache or Cache-Control: max-age=0 . These directives instruct the browser to revalidate the content before serving it, and they don't affect a page's bfcache eligibility.

Note that when a page is restored from bfcache, it is restored from memory, not from the HTTP cache. As a result, directives like Cache-Control: no-cache or Cache-Control: max-age=0 are not taken into account, and no revalidation occurs before the content is displayed to the user.

This is still likely a better user experience, however, as bfcache restores are instant and—since pages don't stay in the bfcache for very long—it's unlikely that the content is out of date. However, if your content does change minute-by-minute, you can fetch any updates using the pageshow event, as outlined in the next section.

Update stale or sensitive data after bfcache restore

If your site keeps user state—especially any sensitive user information—that data needs to be updated or cleared after a page is restored from bfcache.

For example, if a user navigates to a checkout page and then updates their shopping cart, a back navigation could potentially expose out-of-date information if a stale page is restored from bfcache.

Another, more critical example is if a user signs out of a site on a public computer and the next user clicks the back button. This could potentially expose private data that the user assumed was cleared when they logged out.

To avoid situations like this, it's good to always update the page after a pageshow event if event.persisted is true :

window.addEventListener('pageshow', (event) => {
  if (event.persisted) {
    // Do any checks and updates to the page
  }
});

While ideally you would update the content in place, for some changes you may want to force a full reload. The following code checks for the presence of a site-specific cookie in the pageshow event and reloads if the cookie is not found:

window.addEventListener('pageshow', (event) => {
  if (event.persisted && !document.cookie.match(/my-cookie)) {
    // Force a reload if the user has logged out.
    location.reload();
  }
});

A reload has the advantage that will still preserve the history (to allow forward navigations), but a redirect may be more appropriate in some cases.

Ads and bfcache restore

It may be tempting to try to avoid the use of bfcache to serve a new set of ads on each back/forward navigation. However, as well as having a performance impact, it is questionable whether such behavior leads to better ad engagement. Users may have noticed an ad they intended to return to click but by reloading rather than restoring from the bfcache they not be able to. Testing this scenario—ideally with an A/B test—is important before making assumptions.

For sites that do want to refresh ads on bfcache restore, then refreshing just the ads on the pageshow event when event.persisted is true allows this to happen without impacting the page performance. Check with your ad provider but here is one example on how to do this with Google Publishing Tag .

Avoid window.opener references

In older browsers, if a page was opened using window.open() from a link with target=_blank , without specifying rel="noopener" , the opening page would have a reference to the window object of the opened page.

In addition to being a security risk , a page with a non-null window.opener reference can't safely be put into bfcache, because that could break any pages attempting to access it.

As a result, it's best to avoid creating window.opener references. You can do this by using rel="noopener" whenever possible (note, this is now the default in all modern browsers). If your site requires opening a window and controlling it through window.postMessage() or directly referencing the window object, neither the opened window nor the opener will be eligible for the bfcache.

Close open connections before the user navigates away

As mentioned previously, when a page is held in the bfcache, it pauses all scheduled JavaScript tasks and resumes them when the page is taken out of the cache.

If these scheduled JavaScript tasks are only accessing DOM APIs—or other APIs isolated to just the current page—then pausing these tasks while the page is not visible to the user is not going to cause any problems.

However, if these tasks are connected to APIs that are also accessible from other pages in the same origin (for example: IndexedDB, Web Locks, WebSockets) this can be problematic because pausing these tasks may prevent code in other tabs from running.

As a result, some browsers won't attempt to put a page in bfcache in the following scenarios:

If your page is using any of these APIs, we strongly recommend closing connections and removing or disconnecting observers during the pagehide or freeze event. That allows the browser to safely cache the page without the risk of affecting other open tabs.

Then, if the page is restored from the bfcache, you can reopen or reconnect to those APIs during the pageshow or resume event, or have them always automatically reopen using the error or close events. Ensure you do no open multiple connections if using multiple events.

The following example shows how to ensure that pages using IndexedDB are eligible for bfcache by closing an open connection in the pagehide event listener:

let dbPromise;
function openDB() {
  if (!dbPromise) {
    dbPromise = new Promise((resolve, reject) => {
      const req = indexedDB.open('my-db', 1);
      req.onupgradeneeded = () => req.result.createObjectStore('keyval');
      req.onerror = () => reject(req.error);
      req.onsuccess = () => resolve(req.result);
    });
  }
  return dbPromise;
}

// Close the connection to the database when the user leaves.
window.addEventListener('pagehide', () => {
  if (dbPromise) {
    dbPromise.then(db => db.close());
    dbPromise = null;
  }
});

// Open the connection when the page is loaded or restored from bfcache.
window.addEventListener('pageshow', () => openDB());

Test to ensure your pages are cacheable

Chrome DevTools can help you test your pages to ensure they're optimized for bfcache, and identify any issues that might prevent them from being eligible.

To test a page:

  1. Navigate to the page in Chrome.
  2. In DevTools, go to Application -> Back-forward Cache .
  3. Click the Run Test button. DevTools then tries to navigate away and back to determine whether the page can be restored from bfcache.
Back-forward cache panel in DevTools
The Back-forward Cache panel in DevTools.

If the test is successful, the panel reports "Restored from back-forward cache".

DevTools reporting a page was successfully restored from bfcache
A successfully restored page.

If it's unsuccessful, the panel indicates the reason why. If the reason is something you can address as a developer, the panel marks it as Actionable .

DevTools reporting failure to restore a page from bfcache
A failed bfcache test with an actionable result.

In this example, the use of an unload event listener makes the page ineligible for bfcache. You can fix that by switching from unload to using pagehide :

انجام دهید
window.addEventListener('pagehide', ...);
نکن
window.addEventListener('unload', ...);

Lighthouse 10.0 also added a bfcache audit , which performs a similar test. For more information, see the bfcache audit's docs .

How bfcache affects analytics and performance measurement

If you use an analytics tool to measure visits to your site, you might notice a decrease in the total number of pageviews reported as Chrome enables bfcache for more users.

In fact, you're likely already underreporting pageviews from other browsers that implement bfcache, because many popular analytics libraries don't measure bfcache restores as new pageviews.

To include bfcache restores in your pageview count, set listeners for the pageshow event and check the persisted property.

The following example shows how to do this with Google Analytics. Other analytics tools likely use similar logic:

// Send a pageview when the page is first loaded.
// This happens by default just by loading gtag
gtag('config', 'TAG_ID');

window.addEventListener('pageshow', (event) => {
  // Send another pageview if the page is restored from bfcache.
  if (event.persisted) {
    gtag('event', 'page_view');
  }
});

Measure your bfcache hit ratio

You may also want to measure whether the bfcache was used, to help identify pages that are not utilizing the bfcache. This can be done by measuring the navigation type for page loads:

// Send a navigation_type when the page is first loaded.
// To do this disable the default pageview so you can manually send it
// supplemented with the additional detail.
gtag('config', 'TAG_ID', { send_page_view: false });
gtag('event', 'page_view', {
   'navigation_type': performance.getEntriesByType('navigation')[0].type;
});

window.addEventListener('pageshow', (event) => {
  if (event.persisted) {
    // Send another pageview if the page is restored from bfcache.
    gtag('event', 'page_view', {
      'navigation_type': 'back_forward_cache';
    });
  }
});

Calculate your bfcache hit ratio using the counts for back_forward navigations and back_forward_cache navigations.

It is important to realize that there are a number of scenarios, outside of the site owners control, when a Back/Forward navigation won't use the bfcache, including:

  • when the user quits the browser and starts it again
  • when the user duplicates a tab
  • when the user closes a tab and reopens it

In some of these cases the original navigation type may be preserved by some browsers and so may show a type of back_forward despite these not being Back/Forward navigations.

Even without those exclusions the bfcache will be discarded after a period to conserve memory.

So, website owners shouldn't be expecting a 100% bfcache hit ratio for all back_forward navigations. However, measuring their ratio can be useful to identify pages where the page itself is preventing bfcache usage for a high proportion of back and forward navigations.

The Chrome team has added the NotRestoredReasons API to help expose the reasons why pages don't use bfcache, so developers can improve their bfcache hit rates. The Chrome team has also added navigation types to CrUX making it possible to see the number of bfcache navigations even without measuring it yourself.

اندازه‌گیری عملکرد

bfcache can also negatively affect performance metrics collected in the field , specifically metrics that measure page load times.

Since bfcache navigations restore an existing page rather than initiate a new page load, the total number of page loads collected will decrease when bfcache is enabled. What's critical, though, is that the page loads being replaced by bfcache restores would likely have been some of the fastest page loads in your dataset. This is because back and forward navigations, by definition, are repeat visits, and repeat page loads are generally faster than page loads from first time visitors (due to HTTP caching , as mentioned earlier).

The result is fewer fast page loads in your dataset, which will likely skew the distribution slower—despite the fact that the performance experienced by the user has probably improved!

There are a few ways to deal with this issue. One is to annotate all page load metrics with their respective navigation type : navigate , reload , back_forward , or prerender . This lets you continue to monitor your performance within these navigation types, even if the overall distribution skews negative. We recommend this approach for non-user-centric page load metrics like Time to First Byte (TTFB) .

For user-centric metrics like the Core Web Vitals , a better option is to report a value that more accurately represents what the user experiences.

Impact on Core Web Vitals

Core Web Vitals measure the user's experience of a web page across a variety of dimensions (loading speed, interactivity, visual stability), and since users experience bfcache restores as faster navigations than full page loads, it's important that the Core Web Vitals metrics reflect this. After all, a user doesn't care whether or not bfcache was enabled, they just care that the navigation was fast!

Tools that collect and report on the Core Web Vitals metrics, like the Chrome User Experience Report , treat bfcache restores as separate page visits in their dataset. And while there aren't dedicated web performance APIs for measuring these metrics after bfcache restores, you can approximate their values using existing web APIs:

  • For Largest Contentful Paint (LCP) , use the delta between the pageshow event's timestamp and the timestamp of the next painted frame, because all elements in the frame will be painted at the same time. In the case of a bfcache restore, LCP and FCP are the same.
  • For Interaction to Next Paint (INP) , keep using your existing Performance Observer, but reset the current INP value to 0.
  • For Cumulative Layout Shift (CLS) , keep using your existing Performance Observer, but reset the current CLS value to 0.

For more details on how bfcache affects each metric, see the individual Core Web Vitals metric guides pages . For a specific example of how to implement bfcache versions of these metrics, refer to the PR adding them to the web-vitals JS library .

The web-vitals JavaScript library supports bfcache restores in the metrics it reports.

Additional Resources