🍔

router.push()でもprefetchしてスムーズにページ遷移したい、を叶えるカスタムフックを作りました。

2021/03/17に公開

https://www.npmjs.com/package/@dc7290/next-router-prefetch

router.push()だと自動で prefetch されない

Next.js でルート間をクライアント側で遷移(CSR:client side routing)するときにまず使うのは
next/link から提供されるLinkコンポーネントだと思います。
こちらの場合は、該当のコンポーネントがビューポートに入ったら
リンク先を自動で prefetch してくれるので、スムーズにページ遷移されます。
参考: https://nextjs.org/docs/api-reference/next/link

しかし、Linkコンポーネントによる CSR ではなく
next/router から提供されるuseRouterを使って
router.push()で遷移させる時があるかと思います。
こちらの方法でも prefetch するには、router.prefetch()を行う必要があります。

これをLinkコンポーネントと同様の方法で、楽にできたらいいなと思い、
一連のロジックをカスタムフックに隠蔽しました。

こんな人に最適です!

  • router.push() にも prefetch を適用したい!
  • IntersectionObserver を使ったロジックをいちいち書くのはいやだ。
  • pathpida を使っていて、prefetch に渡す値のためにわざわざ文字列に変換したくない。

また、要素がビューポートに入ってから prefetch するのではなく、すぐに prefetch する動作もサポートしていますので、手軽な router フックとしてもご活用ください。

使い方

まずは create-next-app 等を使って、
Next.js のプロジェクトを立ち上げてください。

そして、ライブラリをインストールします。

yarn add @dc7290/next-router-prefetch

先にこちらが今回のカスタムフックのインターフェースになります。

useRouterPrefetch(url, observe, nextRouterOptions);

そして、router.push()を実行するコンポーネントでの実際の使い方がこちらになります。

FooComponent.tsx
import React from "react";
import { useRouterPrefetch } from '@dc7290/next-router-prefetch';

const FooComponent: React.VFC = () => {
  const {
    handleRouterPush,
    prefetchTarget,
  } = useRouterPrefetch<HTMLDivElement>('/foo');

  return (
    <div ref={prefetchTarget} onClick={handleRouterPush}>
      Link to 'foo' page.
    </div>
  );
};

(JavaScript での使用例はこちらへ)

これで、div タグがビューポートに入った時、リンク先を prefetch して、クリックしたら高速でページ遷移します。(もちろんページ先の処理によります笑)

IntersectionObserver を用いない、すぐに prefetch させる場合の使用法も記述します。

FooComponent.tsx
import React, { useEffect } from "react";
import { useRouterPrefetch } from '@dc7290/next-router-prefetch';

const FooComponent: React.VFC = () => {
  const { handleRouterPush } = useRouterPrefetch("/bar", false);
  useEffect(() => {
    if (login) {
      handleRouterPush();
    }
  });

  return <div>Example login page.</div>;
};

では詳しく説明をさせていただきます。

引数

第1引数

第1引数にはリンク先を代入します。
このリンク先は router.push で使われているものと同じものが使用できるので、文字列だけでなく UrlObject でも構いません。
本来 router.prefetch には文字列しか受け取らないのですが、そちらにも適用できるように受け取った UrlObject を内部で文字列に変換しているので、prefetch 用に文字列を渡す必要はありません。

この機能によって、pathpida と併用した時の使い勝手がとても良いです!
pathpida ではpagesPath.$url()とすることで、hash や query を処理した上で UrlObject を返してくれるので、
こちらをそのまま第 1 引数に代入してくれれば prefetch も同時にできるというわけです!

第2引数

第2引数には IntersectionObserver を使って prefetch を行うかを boolean 値で渡します。
デフォルトは true です。
false を渡すとレンダリングのあと、すぐにリンク先を prefetch します。

第3引数

router.push のデフォルトの optins と同様になっていて、
以下の key をもつオブジェクトです。

key value description
as string or UrlObject ブラウザに表示される URL のオプションのデコレーターです。Next.js 9.5.3 より前のバージョンでは、このデコレーターはダイナミックルートに使用されていましたが、その仕組みについては以前のドキュメントをご覧ください。
options object 以下の設定オプションを持つオプションオブジェクトです。
scroll: ナビゲーション後にページの先頭にスクロールするかどうか。デフォルトは true です。
shallow: getStaticProps、getServerSideProps、getInitialProps を再実行することなく、現在のページのパスを更新します。デフォルトは false です。
locale: アクティブなロケールは自動的に前置されます。 locale は異なるロケールを指定することができます。false の場合、href はロケールを含めなければならず、デフォルトの動作は無効になります。

返り値

第2引数(observe)がtrueなら
handleRouterPushprefetchTarget
falseなら
handleRouterPushを返します。

handleRouterPushはその名の通り、渡したリンク先に遷移する関数です。
トリガーにしたいイベント、または useEffect 等の中で使います。

prefetchTargetIntersectionObserverで observe するようにされている ref オブジェクトです。
これをビューポートに入ったら prefetch して欲しい要素の ref に設定します。

ブラウザーサポート

ご存知の通り、IntersectionObserverは IE では動きません。。
IE 対応の必要がある場合は
https://polyfill.io/v3/polyfill.min.js?features=IntersectionObserver
こちらを script タグで挿入するか、

https://github.com/w3c/IntersectionObserver/blob/main/polyfill/Intersection-observer.js
こちらを参考にしてみてください。

今後の改善予定

現在、改善案として
IntersectionObserverの options も渡せるようにする
というものを考えています。

ユースケースとして、ビューポートに入る少し前から prefetch したいというのが考えられるかなと思っていて、
それを実現するならいっそのことIntersectionObserverの options を渡せてしまったほうがいいかなと考えています。

余談

今回初めてライブラリ開発をしたのですが、少しはエンジニア力が上がったかなと思います!
何より雲の上と思っていたライブラリ開発をしているというだけで、嬉しくなりました笑

また、今回失敗したな、と思うことがあって、
セマンティックバージョニング?というものを理解できていなかった生で、
初期段階なのにもう version が 2 に上がってしまいました。。。

ただ、これでいつも使っているライブラリがどういう仕組みでバージョニングしているかを知れたのでいい機会だったと思うことにします!

参考

https://github.com/aspida/pathpida

https://www.npmjs.com/package/@dc7290/next-router-prefetch

Discussion