Использование подсказок пререндеринга может значительно сократить воспринимаемое время загрузки веб-страниц, что обеспечивает отличный пользовательский опыт (в браузерах, которые это поддерживают). Однако есть сценарии, в которых мы можем захотеть отменить ожидающую операцию предварительного рендеринга, чтобы избежать артефактов с динамическим содержимым.

TL;DR (просто покажите мне код)

Единственный надежный кросс-браузерный метод отмены незавершенной операции предварительного рендеринга, который мне удалось найти, — это изменение атрибута href элемента <link> на другой, но действительный URL-адрес, предпочтительно ведущий на страницу, которая является «дешевой» для скачать и рендерить. Затем удалите элемент <link>, чтобы полностью прервать пререндеринг в некоторых браузерах. Нравится:

var hint = document.querySelector('link[rel=prerender]');
hint.setAttribute('href', '//example.org');
hint.parentNode.removeChild(hint);

Примечание. Приведенный выше адресexample.orgявляется всего лишь примером — но на самом деле его можно использовать, если у вас нет собственной соответствующей страницы (это зарезервировано IANA, он весит менее 1 КБ в сжатом виде и не имеет дополнительных зависимостей).

Предварительный рендеринг динамического контента является сложной задачей

Рассмотрим типичный сайт электронной коммерции. В нем много страниц продуктов, которые меняются относительно редко, что означает, что они, вероятно, могут быть предварительно отрисованы (при условии, что мы можем достаточно точно предсказать, на какую страницу перейдет пользователь в любой момент времени). Но есть также корзина покупок, которая по своей природе более динамичнаПредставьте себе следующее:

  1. Пользователь посещает /products/a.html. Корзина покупок, отображаемая в виде виджета на странице, пуста.
  2. Основываясь на предыдущих посещениях, мы прогнозируем, что пользователь перейдет к следующему /products/b.html, и поэтому вводим соответствующую подсказку предварительного рендеринга (<link rel=”prerender” href=”/products/b.html”>).
  3. Страница /products/b.html теперь отображается в фоновом режиме. Пользователь все еще на /products/a.html.
  4. Пользователь решает положить товар на /products/a.html в корзину. Виджет корзины обновлен с помощью AJAX-запроса и теперь показывает «1 товар».
  5. Наконец, пользователь переходит к /products/b.html и получает предварительно обработанную страницу. Очень быстро, но также… запутанно. Корзина пуста!

Проблема, конечно, в том, что /products/b.html был отрендерен до того, как что-то было в корзине, и это версия страницы, которая сейчас отображается пользователю.

Если покупательская корзина была визуализирована клиентом, он может использовать Page Visibility API, чтобы отложить выборку содержимого корзины до тех пор, пока страница не станет видимой. Но если он визуализируется на стороне сервера (как мы предполагаем в этом примере), нет возможности учесть действия, выполняемые пользователем после инициализации пререндеринга.

Итак, у нас есть проблема. Мы хотим использовать предварительную визуализацию для улучшения взаимодействия с пользователем при просмотре каталога товаров, но мы не хотим рисковать отображением непоследовательного содержимого корзины (что может привести к ненужным звонкам в службу поддержки или упущенным продажам).

Аннулирование незавершенных операций пререндеринга

Что я хотел бы сделать в этом случае, так это включить подсказки предварительного рендеринга, но аннулировать любой ожидающий предварительный рендеринг, если пользователь кладет товар в корзину. Это подводит нас к вопросу: как мы можем надежно отменить операцию предварительного рендеринга?

В идеальном мире мы могли бы просто взглянуть на спецификацию и найти это:

Пользовательский агент должен прервать запрос, если атрибут href в элементе link ссылки-подсказки удален или его значение установлено на пустую строку.
Источник: Спецификация W3C подсказок ресурсов

Если вы попробуете это на практике, то обнаружите, что мир действительно (сюрприз!) не совершенен. Internet Explorer 11 предполагает, что вы хотите предварительно отобразить относительный URL, соответствующий пустой строке (в нашем случае /products/); исходный предварительный рендер отбрасывается, но вместо этого рендерится потенциально ресурсоемкая страница, которая, насколько нам известно, не будет показана пользователю. (Chrome и Opera ведут себя немного по-разному и прерывают пререндеринг с сообщением об ошибке «Неподдерживаемая схема»; только http и https поддерживаются схемы для предварительного рендеринга.)

После некоторых экспериментов я обнаружил, что и Chrome, и Opera отменяют пререндеринг, если инициирующий элемент <link> удаляется из DOM, что довольно удобно. Однако в Internet Explorer это не так.

Как насчет установки href на about:blank? Это также неподдерживаемая схема, из-за которой Chrome и Opera отменят первый предварительный рендеринг, но оказывается, что Internet Explorer просто жалуется на схему нового URL, не отбрасывая старый.

Что похоже, работает стабильно в разных браузерах, так это изменение href на допустимый, поддерживаемый URL. Это означает, что мы снова указываем браузеру отображать страницу, которая будет просто выброшена, но мы можем свести к минимуму потери ресурсов, выбрав «дешевую» страницу.

В идеале у вас должна быть пустая страница в том же домене, что и текущая страница (чтобы избежать рукопожатий TLS, разрешения DNS и т. д.), но если вы не хотите настраивать такую ​​страницу, вы можете использовать example.org. Он зарезервирован IANA в качестве примера веб-страницы, что означает, что он, вероятно, будет существовать в обозримом будущем. Это также просто статический HTML без каких-либо внешних зависимостей (таких как изображения, таблицы стилей или скрипты) и весит около 1 КБ в сжатом виде. Также он доступен как по http, так и по https.

Окончательное решение

Комбинируя эти подходы, мы получаем что-то вроде этого:

// Get the link element that initiated the original prerender.
// This assumes there is only one such element, but you could
// easily adapt the code to work in a situation where multiple
// hints are present.
var hint = document.querySelector('link[rel=prerender]');
// All browsers seem to allow only one page being prerendered
// at a time (presumably to keep resource usage to a minimum).
// We can therefore instruct the browser to prerender a "cheap"
// page to invalidate any previously prerendered page.
// You could use example.org (as below) if you don't have a
// suitable page of your own.
hint.setAttribute('href', '//example.org');
// Some browsers (Chrome and Opera) detect when a
// resource hint element is no longer present in the DOM
// and discard any pending operation related to it.
hint.parentNode.removeChild(hint);

Возможно, не так чисто, как хотелось бы, но и не так уж плохо.