8
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Qiita株式会社Advent Calendar 2021

Day 23

ResourceHintsを簡単に使えるようにするためのカスタムフックの紹介

Last updated at Posted at 2021-12-22

はじめに

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の技術について情報交換をしたい方がいれば、ぜひこちらから気になるをよろしくお願いします!

8
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?