はじめに
Qiita株式会社 Advent Calendar 2021の23日目(2)の担当は、Qiita株式会社プロダクト開発グループの@getty104です!
これは何
Qiitaでは、フィードページなどで記事ページのロードを高速化するために、ResourceHintsを用いたページのprefetchを行っています。
これをQiitaではどう実現しているかを紹介します。
ResourceHintsって何
細かい説明は省きます。ページの事前読み込みを行うためのブラウザの機構です。
詳しい説明は以下をご覧ください。
ResourceHintsを使う方法
↓のようなタグをHTMLのheadに追加します。
<link rel="prefetch" href="/" as="document">
↑のタグをQiitaに追加するとすると、Qiitaのトップページをprefetch
してくることになります。
prefetch
は「ブラウザのリソース取得までのフロー」のリソースの取得まで事前に行います。
よって、このタグを追加することで、次にトップページに訪れるとトップページはリソースの取得はスキップし、描画から始めることができるようになり、ページ表示速度が高速化します。
悩みどころ
ResourceHintsの仕組みとしては前項の内容の通りなのですが、一つ悩みどころがあります。
それは「prefetchするリソースをいかに動的に取得するか」です。
単純にprefetch用のタグを事前に埋め込むとすると、訪れなかったページのprefetchは余分になります。
逆に全てのページのprefetchを行わずに特定のページのみのprefetchを行うとすると、prefetchできないページに訪れる確率が上がり、思ったほどの高速化は見込めません。
Qiitaの場合フィードページは記事は初期ロードで30、またランキングやその他リンクなど、数え切れないほどのリンクが存在します。
その中からユーザーが実際に訪れるページを予測するのはとても難しいです。
ResourceHintsを実際に使うとすると、以下のような条件を満たした運用を行いたいと思うはずです。
- 不要なページのprefetchは行わない
- 訪れるページのprefetchは(ほぼ)必ず行う
解決方法
QiitaではフロントエンドにReactを用いており、Reactのカスタムフックで動的にResourceHintsの実行を行う機構を作っています。
ResourceHintsのカスタムフックは以下のようなものです
import { useCallback, useRef } from 'react'
interface FetchType {
rel: 'dns-prefetch' | 'preconnect' | 'prefetch'
url: string
resourceType: 'document' | 'image' | 'script' | 'style'
}
export const useResourceHints = () => {
const fetchedRef = useRef(new Set<string>())
const fetchResource = useCallback(({ rel, url, resourceType }: FetchType) => {
if (document && !fetchedRef.current.has(url)) {
const linkElement = document.createElement('link')
linkElement.rel = rel
linkElement.href = url
linkElement.as = resourceType
document.head.appendChild(linkElement)
fetchedRef.current.add(url)
}
}, [])
return { fetchResource }
}
このカスタムフックは、以下のような処理を実現します
- fetchResource({rel, url ,resourceType })を実行する
- urlが以前prefetch済みだったら→何もしない
- urlがまだprefetchしたことがなかったら→
head
タグ内に以下のようなタグを追加する<link rel={rel} url={url} as={resourceType}></link>
- このタグを追加したタイミングで、タグの処理が発火しprefetchが始まる
このカスタムフックはコンポーネント内で以下のように利用しています。
const Component = ({ url }: { url: string }) => {
const { fetchResource } = useResourceHints()
return(
<a href={url} onMouseEnter={() => fetchResource({rel: 'prefetch', url: url, resouceType: 'document' })}>
Click me!
</a>
)
}
このようにonMouseEnter
のイベントでfetchResource
を実行するようにすることで
リンク先のページに訪れようとする→リンクの上にカーソルを持ってくる→事前にデータをprefetchする→クリックする→ページを移動する
という処理を実現しています。これがまさに先ほど上げた条件の
- 不要なページのprefetchは行わない
- 訪れるページのprefetchは(ほぼ)必ず行う
を実現している方法になります。
課題
このような方法でprefetchを過不足なく行う状態を実現していますが、この方法にもまだ課題があると思っています。
課題は「prefetchが完了するまでにリンクをクリックしてしまうとページの高速化は行えない」ことです。
これはページ自体の表示速度を上げ、prefetch
が完了するまでの速度を上げていくしかありません。
Qiitaのパフォーマンスを上げることはそのままユーザー体験の向上につながります。
より快適な体験を提供するためにも、引き続きQiitaの改善を行っていきます。
最後に
Qiita株式会社ではMeetyを開設しています。
Qiitaの技術について情報交換をしたい方がいれば、ぜひこちらから気になるをよろしくお願いします!