【Next.js / App Router】Linkコンポーネントによるページ遷移前にプログレスバーを表示させたい!
こんにちは!@Ryo54388667です!☺️
普段は都内でフロントエンドエンジニアとして業務をしてます!
主にTypeScriptやNext.jsといった技術を触っています。
今回はLinkコンポーネントによるページ遷移前にプログレスバーを表示する方法について紹介したいと思います!
📌 課題
まず、遷移前のプログレスバーがどのようなものかというと、次の動画のものです。
GitHubのGUIにも用いられていて、タブを切り替える際、画面の左上から右上にかけて青色のバーが横断する形で延伸します。
このUIは処理が進行中であることをユーザーに伝える役割を担っています。遷移先のページが初期表示に時間がかかる場合に頻繁に利用されます。
こちらの実装方法ですが、App Router以前は、_app.tsx
にNext.jsの組み込みライブラリーのnext/routerのrouter.events
を利用して実装することが多かったです。Web上の記事でもそのような実装例が多いですね。
import { useEffect } from 'react'
import { useRouter } from 'next/router'
export default function MyApp({ Component, pageProps }) {
const router = useRouter()
useEffect(() => {
const handleRouteChangeError = (err, url) => {
if (err.cancelled) {
console.log(`Route to ${url} was cancelled!`)
}
}
router.events.on('routeChangeError', handleRouteChangeError)
// If the component is unmounted, unsubscribe
// from the event with the `off` method:
return () => {
router.events.off('routeChangeError', handleRouteChangeError)
}
}, [router])
return <Component {...pageProps} />
}
しかし、App Router 標準のnext/navigation のrouter機能には以前のrouter.eventsに相当するようなものがありません😇詳しくは下記のDiscussionを見てください!
Pages RouterからApp Routerへ大きくインターフェースが変更されたので仕方のないことではありますが、後方互換性がないのは少々ツラいですね。。
📌 実装について
結論
今回、プログレスバー自体の実装についてはスコープ外なのでライブラリーを利用させていただきます。NProgressというライブラリーを利用します。
Package
name | version |
---|---|
Next | 14.0.0 |
React | 18.2.0 |
nprogress | 0.2.0 |
まず結論ですが、
/components/navigation-events.tsx
'use client'
import { useEffect } from 'react'
import NProgress from "nprogress";
import 'nprogress/nprogress.css'
type PushStateInput = [data: any, unused: string, url?: string | URL | null | undefined];
export function NavigationEvents() {
const handleAnchorClick = () => {
NProgress.start()
};
const handleMutation: MutationCallback = () => {
const anchorElements = document.querySelectorAll('a');
anchorElements.forEach((anchor) => anchor.addEventListener('click', handleAnchorClick));
};
useEffect(() => {
const mutationObserver = new MutationObserver(handleMutation);
mutationObserver.observe(document, { childList: true, subtree: true });
window.history.pushState = new Proxy(window.history.pushState, {
apply: (target, thisArg, argArray: PushStateInput) => {
NProgress.done();
return target.apply(thisArg, argArray);
},
});
}, [])
return null
}
/app/layout.tsx
import { Suspense } from 'react'
import { NavigationEvents } from './components/navigation-events'
export default function Layout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
{children}
<Suspense fallback={null}>
<NavigationEvents />
</Suspense>
</body>
</html>
)
}
詳細
なかばパワープレイで恐縮ですが、全体の流れは次のような感じです。
- DOMの監視を行い、変更を検知する
- ページ内のaダグを取得し、clickイベントを追加する
- historyAPIの挙動の中にプログレスバーの処理を追加する
今回、最も単純な形で実装をしました。
実際のところ、まだ課題はたくさんあります!
たとえば、aタグの中にはtel:
のように電話番号を呼び出すものもあるので、それを考慮する必要があります。さらに、target属性が_blank
の場合はプログレスバーは不要です。
今回はdocument全体を監視の対象としているのですが、意図しない副作用があるかもしれないので特定のDOMに絞ることも必要かと思います。
今回、手弁当で実装してみましたが、実務で利用するなら考慮事項も多いので素直にライブラリーを利用するのをおすすめします!ここにきて、ちゃぶ台返しもいいところですが。。笑
僕が調べた中ではこちらのライブラリーが良さそうです✨インターフェースもシンプルですし。
参考
📌 まとめ
- aタグにクリックイベントを追加し、プログレスバーのスタート処理を追加する。
- historyAPIにプラグレスバーのエンド処理を追加する。
より良い方法があれば教えてください〜
最後まで読んでいただきありがとうございます!
気ままにつぶやいているので、気軽にフォローをお願いします!🥺
個人メディアもあります〜
Discussion