自作サイトを自作ライブラリで5倍読み込み速度を高速化した話

こんにちは。[ぬこすけ](https://twitter.com/nuko_suke_dev) です。

随分前なのですが、 React で手軽にサイトを高速化できるライブラリ `react-dom-lazyload-component` を作って公開しました。

https://github.com/hiroki0525/react-dom-lazyload-component

ふと、「本当に **サイト高速化の効果があるのか？** 」と思い、自作サイトに `react-dom-lazyload-component` を使ってみました。

**自作ライブラリが本当にサイト高速化に役立つのか**、検証したいと思います。

## `react-dom-lazyload-component` ってどんなライブラリ？

**ブラウザの決まった枠内に入るまで DOM を非表示にするライブラリです** 。

`IntersectionObserver` というブラウザ API をご存知の方はピンと来るかもしれません。

![スクリーンショット 2022-11-18 9.04.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164159/bbf8d435-a330-aeb1-0409-5beee171a98c.png)

上の画像のように、 Viewport という領域があります。
Viewport という領域は自分で調整できますが、一旦、ユーザーが開いているブラウザの表示領域だと思ってください。

ユーザーがブラウザをスクロールしていき、赤い四角の「 Visible!! 」のように対象の DOM が Viewport に入ると DOM が描画されます。

一方で Viewport 外の DOM はグレー四角の「 Invisible 」のように DOM は描画されません。

こうすることで **必要なタイミングで必要な UI だけ描画** されるようになり、 **サイトの表示高速化** が期待されます。

特に、 **必要なタイミングで UI 描画に必要なリソース（ JavaScript など）を取得** するようにすれば **初期描画の高速化** もできます。

## 自作したサイトってどんなサイト？

この `react-dom-lazyload-component` の実験台の対象となるのは「 [Instagram Hashtag Translator](https://instagram-hashtag-translator.com/) 」 というサイトです。

https://instagram-hashtag-translator.com/

どんなサイトかいうと、 **インスタグラムの投稿で外国語でハッシュタグをつけたい時に、インスタグラム向けにハッシュタグを翻訳し、コピペして投稿できるようにするツール** です。

例えば、「猫 カフェ」で英語も含めてハッシュタグを用意したい場合は、「#猫 #カフェ #cat #cafe」という形でアウトプットを出してくれます。

![chrome-capture-2022-10-18 (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164159/ce5f1607-e05c-777a-883b-54281b4825fb.gif)

企画の背景や技術スタックなど、詳しい話は [Qiita の記事](https://qiita.com/nuko-suke/items/8765118a3020692083c2) でまとめているので、ご興味あればご覧ください。

https://qiita.com/nuko-suke/items/8765118a3020692083c2

## いざ、検証！の前に

自作ライブラリを使ってどのくらいサイト高速化できるか検証する前に、[Instagram Hashtag Translator](https://instagram-hashtag-translator.com/) の簡単な前提条件を確認しておきましょう。

- 使っている主なライブラリとバージョン
  - React v18.2.0
  - Next.js v13.0.3
    - `app` Directory といった v13 の最新機能は使っていません。
- インフラ
  - Vercel

また、 `react-dom-lazyload-component` の導入前の PageSpeed Insights の実行結果は次の通りです。

- PC
![before-pc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164159/01010928-3dc5-d3c3-36c6-f55006afc47c.png)

- モバイル
![before-sp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164159/82fcd5d1-4d1e-4b99-3312-f28f5f3dd27a.png)

遅い！笑

ここからどのくらい高速化するのか、見ていきましょう！

## `react-dom-lazyload-component` を使った実装

どのように実装していくかも触れておきます。
次のような `react-dom-lazyload-component` をラップしたコンポーネントを用意します。

```tsx
import { FC, ReactNode } from 'react';
import LazyLoad, { LazyLoadProps } from 'react-dom-lazyload-component';
import LoadingIcon from '~/components/atoms/icons/LoadingIcon';

type LazyProps = Pick<LazyLoadProps, 'as' | 'suspense'> & {
  readonly className?: string;
  readonly children: ReactNode;
  readonly id?: string;
};

const Lazy: FC<LazyProps> = ({ className, children, as, suspense, id }) => (
  <LazyLoad
    className={className}
    as={as} // div や span など使いたいタグ名。 
    fallback={<LoadingIcon />} // Viewport 外で表示する UI と React.Suspense の fallback
    suspense={suspense} // React.Suspense を使うかどうか
    id={id} // DOM の id
  >
    {children}
  </LazyLoad>
);

export default Lazy;
```

この `Lazy` コンポーネントはブラウザの表示領域に入るまで `LoadingIcon` という読み込み中の UI を表示し、表示領域に入ると `children` を表示させます。

`Lazy` を使う側として、ユーザーのファーストビューで表示されないフッターのコンポーネント例をあげます。

```tsx
import dynamic from 'next/dynamic';
import { FC } from 'react';
import Lazy from '~/components/atoms/Lazy';

const Copyright = dynamic(() => import('~/components/atoms/Copyright'), {
  suspense: true,
});

const Footer: FC = (): JSX.Element => (
  <Lazy
    className='クラス名入れる'
    as='footer'
    suspense
  >
    <Copyright />
  </Lazy>
);

export default Footer;
```

この `Footer` コンポーネントはブラウザの表示領域に入った瞬間に表示されます。

１つポイントとしては **next/dynamic を使ってコンポーネントの遅延読み込み** をしている点です。

このようにすることで、`Footer` コンポーネントがブラウザの表示領域に入った時に初めて `Copyright` コンポーネントが読み込まれるため、 **初期描画が早くなるメリット** があります。

`next/dynamic` を使った例ですが、 `React.lazy` を使っても良いでしょう。

このフッターのようにユーザーのファーストビューで表示されないコンポーネントを `react-dom-lazyload-component` と `next/dynamic` を組み合わせて修正をしていきます。

## `react-dom-lazyload-component` を使った結果

PageSpeed Insights の結果はこうなりました！

- PC
![after-pc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164159/5ecc71dc-0088-8c0b-d1ab-5085c97f6302.png)

- モバイル
![after-sp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164159/27a87ac9-3ab5-107d-5abf-d846562f7740.png)

**PC は 39 → 70 に爆上がりしました** 。良さげです🥳。
「読み込み中」の UI ( `LoadingIcon` ) で高さを指定をしていないので、「 Cumulative Layout Shift 」 は上がっちゃいました😜。

モバイルは 34 → 45 なのでスコア的にはそこまでアップしていませんが、 **「 First Contenful Paint 」 や 「 Speed Index 」などの各指標が 4 ~ 5 倍ほど改善** されています。
これもまた良さげです🥳。

## まとめ

自分で作ったサイト「 [Instagram Hashtag Translator](https://instagram-hashtag-translator.com/) 」 を、これもまた自分で作ったライブラリ「 [react-dom-lazyload-component](https://github.com/hiroki0525/react-dom-lazyload-component) 」を使ってサイトを高速化するお話でした。

ぜひ `react-dom-lazyload-component` を使ってみてください！

https://github.com/hiroki0525/react-dom-lazyload-component

もしバグや使い方がわからない場合は日本語でも OK なので、 Github の issue なりにコメントいただければと思います!

今回は React のパフォーマンス改善例でしたが、フロントエンド全般のパフォーマンス改善のハウツーをまとめた記事もあるので、こちらもご参考にしていただければと思います！

https://qiita.com/nuko-suke/items/50ba4e35289e98d95753

最後に [Twitter](https://twitter.com/nuko_suke_dev) もやっているのでぜひフォローください！

ここまでご覧いただきありがとうございました！