コンテンツへスキップ

【Next.js】Next.js 13.4でページ移動を検知してページローディングアニメーションを実装する方法

  • 未分類

Next.jsはバージョン13.4になってから様々な変化をしました。

例えばApp Routerの導入やgetServerSideProps関数などを代表する様々なサーバーサイド処理向け関数の廃止や非推奨化など、部分的には今までのNext.jsによる開発方法が使えなくなってしまうということもあります。

Next.jsによる開発で実装方法を調べようにも、古いバージョンの情報しか出てこなく、情報収集に苦戦するということもあるでしょう。

そこで今回は、Next.js 13.4でページ移動を検知して、ページ移動操作時にページローディングアニメーションを実装する方法をまとめていきます。

App RouterではuseRouterを使わなくなった

Next.js 13.4の前までのバージョンでは、パスの管理やページ遷移に関わるイベントの管理をnext/routerのuseRouterが行ってくれていました。

ただ、バージョン13.4になってからApp Routerのもとではパスの管理などをnext/navigationのusePathnameやuseSearchParamsで行うようになります。

このusePathnameやuseSearchParamsはその名の通り、現在のパス名とそれに付随するクエリパラメータの値を取得するための関数で、URLの変更を検知するようなイベントは持っていません。

usePathnameとuseSearchParamsを使ったページ移動の検知

では実際にusePathnameとuseSearchParamsを使ったページ移動の変更検知の方法を説明します。

'use client'
import { useEffect } from 'react'
import { usePathname, useSearchParams } from 'next/navigation'

export default function Example() {
  const pathname = usePathname()
  const searchParams = useSearchParams()

  // ページ移動を検知
  useEffect(() => {
    alert('ページ移動したよ!')
  }, [pathname, searchParams])

  return (
    {/* JSX */}
  )
}

Next.js 13.4でページ移動の検知を実装する際には、クライアントサイドコンポーネントに実装します。

useEffectでpathnameとsearchParamsの変更を検知し、それをトリガーにページ移動を検知するという仕組みです。

ページローディングアニメーションを実装する

さてusePathnameとuseSearchParamsを利用したページ移動検知の仕組みがわかったところで、次はこれを利用して、ページ移動時にページローディングのアニメーションを実装する方法を解説していきます。

本題に入る前にページローディングの流れを整理しておきたいと思います。

ざっくりとした流れとしてはこのような感じで、ページ表示にページ内のすべてのaタグに対して、ページローディングアニメーションを表示するためのクリックイベントを追加する処理を実行します。

リンク(aタグ)をクリックすると、クリックイベントが発動し、ページローディングのアニメーションが表示されます。

そしてサーバからのレスポンスが返ってきたらページ移動して次の移動してページローディングアニメーションを非表示にして終了という流れになります。

ページローディングの表示 / 非表示ステータス管理

ページローディングを表示するか、非表示にするかの管理は状態管理ライブラリを利用して、一つのコンポーネントに依存させないようにして管理します。

具体例として、以下は状態管理ライブラリのjotaiを利用し、カスタムフックを作るやり方です。

// ローディングアニメーションを表示管理するためのステートを定義
import { atom } from 'jotai'

export const loadingAtom = atom<boolean>(false)
'use client'
import { atom } from 'jotai'

// States
import { loadingAtom } from '@/states/loadingAtom'     // 先ほど定義したステートを呼び出す

export default function useLoading() {
  const [value, setValue] = useAtom(loadingAtom)

  // ローディング開始
  const start = () => {
    setValue(true)
  }

  // ローディング終了
  const stop = () => {
    setValue(false)
  }

  return {
    value,
    start,
    stop
  }
}

ステートの定義とカスタムフックを分離する場合は、上記の2ファイルを用意します。

ページローディングのアニメーションの表示 / 非表示をコントロールするコンポーネントでカスタムフックを呼び出すことで、どのコンポーネントでも簡単に表示 / 非表示の状態管理を行えるようになります。

ページローディングアニメーション用のコンポーネント

さて次はページローディングを表示するためのコンポーネントを用意します。
これは今回のメインのコンポーネントファイルになります。

'use client'
import { useEffect } from 'react'
import { usePathname, useSearchParams } from 'next/navigation'
import Image from 'next/image'

// Hooks
import useLoading from "@/hooks/loading/useLoading"

export default function Loading() {
  const pathname = usePathname()                                                                                              // パス
  const searchParams = useSearchParams()                                                  // クエリパラメータ
  const siteUrl: string = (process.env.NEXT_PUBLIC_SITE_URL)? process.env.NEXT_PUBLIC_SITE_URL : "http://localhost:3000"      // サイトオリジンURL
  const currentUrl: string = siteUrl + pathname                                              // 現在ページのURLパス
  const loading = useLoading()                                                                                                // ページローディングのカスタムフック呼び出し

  // ページローディングを表示させる関数
  function handleStartLoading() {
    loading.start()
  }

  // ページ移動検知
  useEffect(() => {
    const links = document.getElementsByTagName('a')     // 現在表示しているページのすべてのaタグを取得

    // ページ移動完了後にページローディングを非表示にする
    setTimeout(() => {
      // ローディングを停止
      loading.stop()
    }, 1000);

    // 各aタグにイベントリスナーを追加
    for(let i=0; i<links.length; i++) {
      // 現在開いているページ以外に移動するaタグにクリックイベントを追加
      // ※ 現在開いているページにもクリックイベントを追加するとページローディングが消えなくなる可能性が出ちゃう
      if(
        currentUrl !== links[i].href &&
        links[i].href.startsWith(siteUrl)
      ) {
        links[i].addEventListener('click', handleStartLoading)
      }
    }

    // コンポーネント破棄時にイベントリスナーを削除
    return () => {
      for(let i=0; i<links.length; i++) {
        links[i].removeEventListener('click', handleStartLoading)
      }
    }
  }, [pathname, searchParams])

  return (
    <>
    {loading.value &&
      {/* ページローディングアニメーションのHTML記述 */}
    }
    </>
  )
}

このコンポーネントをlayout.tsなどのグローバルなコンポーネントに設置してあげれば、サイト全体にページローディングのアニメーションを実装することができます。

注意してほしい点としては、ページローディング表示用のクリックイベントは、必ず別ページに移動するリンクを対象に設置するようにしてください。

同ページのリンクに対してイベントリスナーを追加した場合、ページローディングアニメーションは表示されるが、ページ移動をしないので、ページローディングアニメーションを非表示にするための処理が実行されず、ページローディングアニメーションがずっと表示される状態になってしまいます。

まとめ

というわけで今回は、Next.js 13.4で新しく実装されたusePathname、useSearchParamsを利用したページ移動の検知処理を利用したページローディングアニメーションの実装方法をご紹介しました。

Next.js 13.4ではuseRouterだけではなく、今まで使えていたサーバーサイドコンポーネントが利用できなくなっているケースが多くあります。

従来のNext.jsでの開発手法が通用しないなんてこともありますので、今後も時間があればNext.js 13.4から登場している様々な関数を紹介していこうと思います。