HTML PrefetchでMPAでInstantClickのようなものを実装する
発端
HotwiredのTurboでInstant Clickっぽいものを実現するPRが作成され、
取り込まれそうな気配でちょっと話題に。
InstantClick
かなり昔からあるライブラリでmouseover
とか touchstart
などをきっかけにプリフェッチを行うライブラリ。
クリックテスト
ここで、Hover -> クリックまでの時間を計測できる。
この時間を短縮するのがInstantClickの役割。
Dev.to(forem)
このInstantClickの仕組みは、dev.toでも利用されていて、軽快なページ遷移に一役買っている。
InstantClickはPjaxなのでMPAでできないか?
InstantClickはPjaxなので、JSでHTMLをレンダリングし直している。
Turboもそうだけど、厳密にはMPAではないような気がするのでTurboやInstantClickを使わないMPAでも同じような事が実現できないか?というのが今回の検証のきっかけ。
Prefetch属性
<link rel="prefetch" href="https://www.example.com/solutions" />
このPrefetch属性を使うことで、リンク先のprefetch、つまり先読みができる。
このPrefetchをmouseoverなどのイベントを拾って、先読みができれば実現できそう。
ブラウザの対応状況
結構昔からある印象だけど、Safari、iOS Safariでは使えないらしい。
Prefetch属性を使って実際にプリフェッチを行う
簡単なサンプルで、HTMLクラスにprefetch
がついているaタグのリンク先をprefetchするコード。
ホバーしたリンク先のlinkタグをdocument.bodyに挿入する。
instantclickはtouchstartだったりでプリフェッチをしているけど割愛。
function preloadLink(event) {
const href = event.currentTarget.getAttribute('href');
const link = document.createElement('link');
link.rel = 'prefetch';
link.href = href;
document.head.appendChild(link);
}
const links = document.querySelectorAll('a.prefetch');
links.forEach(link => {
link.addEventListener('mouseover', prefetchLink);
link.addEventListener('touchstart', prefetchLink);
});
挙動
ざっくりChromeで動かした。振る舞い的にはプリフェッチしてるときにデータを読み込んでも引き継がれ、
例えば2秒かかるページを、プリフェッチして1秒後にアクセスしたら残り1秒読み込んだ段階でページが表示された。
⚠注意点
Cache-Control: no-cache, privateとかだとキャッシュされないらしい。
ブラウザによってキャッシュポリシーが異なるなどの差異については調べていない。
Railsで取り込まれそうなPRについてはキャッシュの保持期間が10秒程度なので、そこに合わせてCache-Controlを設定しておく。
class PagesController < ApplicationController
def show
response.headers["Cache-Control"] = "public, max-age=10"
# その他のアクションのロジック
end
end
まとめ
- 古き良きMPAでどうしても早くしないといけないみたいな案件が有るときに奥の手として使うのはあり。
- prefetchを使って確かにチューニングはできるけど余計なリクエストをしているような感じもするので、絶対に使うものとかではない。
- Safariで使えないのは素直に諦めて、プログレッシブエンハンスメントの考えでやるほうが幸せになれる。
- prefetchについては昔からあったけど、こうやってちゃんと向き合うことで面白さを感じた。
おまけ
Next.jsのPrefetchは異なり、HTMLのPrefetch属性については使ってない。
Developmentのときの振る舞いに近い。
まとめた結果をここに書いてみました。