火曜日, 5月 6, 2025
ホームニューステックニュースNext.js 15.3「ロード中」を作れるカスタムLink (useLinkStatus使用)

Next.js 15.3「ロード中」を作れるカスタムLink (useLinkStatus使用)


Linkコンポーネント共通の課題

これまでLinkコンポーネントを使用していて、下記のような課題を感じていました。

  • 同じLinkコンポーネントを、ロード中も連打出来てしまう
  • ローディング中、見栄えを変えることが出来ない
  • LinkコンポーネントでURL遷移後、スマホ向けサイドバーを閉じることが出来ない

特にサイドバーのリンクに対してこのような悩みを日頃から感じていました。
router.pushで上記の課題に対応していた時もあったんですが、router.pushでは偽物のリンクにしかなれず、なかなか満足いくUXを得られるリンクを作れずにいました。
今回カスタムLinkコンポーネントを作成し、課題に対応することができました。

デモ

カスタムLinkを使用している様子を下記の画像から確認いただけます。

サイドバーの2つのリンクでは、わざと遅く表示されるようにしています。
「Homeへ移動」をクリックすると、Linkコンポーネント全体が「ロード中」に切り替わるのが分かるかと思います。
一方「Introduceへ移動」をクリックした場合では、Loadingのアニメーションが追加された様子が確認できます。
useLinkStatusを使うとUIを足すのは簡単ですが、全体的に見た目を変えるのは何故か難しい。。。
カスタムLinkコンポーネントでは、Linkコンポーネントの中身が全体的にロード中のものに置き換えるような作りを行っています。

カスタムLinkの呼び出し方例

まずは呼び出し方です。
ここでのLinkコンポーネントは、Next.jsのLinkコンポーネントをカスタマイズした別物のLinkコンポーネントです。

const HomeLink = async () => {
 return(
  Link
    href='/home'
    loading={div>ロード中...div>}
  >
    div>Homeへ移動div>
  Link>
 ) 
}

カスタマイズしたLinkコンポーネントにはloadingというpropsがあります。ロード中、別のコンポーネントに切り替えることにより見栄えを変更します。使い勝手はSuspenseコンポーネントのfallbackと同じような感覚です。このような格好でロード中のUIを切り替えすることにより、Server ComponentsもClient Componnetsも同じ開発体験で行えます。
また、このLinkコンポーネントには装飾されたデザインを含んでいません。
readyな時とloadingな時の2つのUIをpropsに渡す作りなので、いままでの開発したLink用のコンポーネントをあまり変えずにこのコンポーネントを使用することができるかもしれません。
装飾されたデザインをロード中に追加したい場合は、下記のような感覚でLoadingIconコンポーネントが付いた、全体の見栄えをloading propsにセットすれば良いと思いました。

  Link
    href='/home'
    loading={div>Introduceへ移動LoadingIcon />div>}
  >
    div>Introduceへ移動div>
  Link>

カスタムLinkのソースコード

この機能を実現するにあたり、Next.js 15.3 useLinkStatusonNavigateを使用しています。

"use client";


import { cn } from "@/lib/utils";
import NextLink, { LinkProps as NextLinkProps, useLinkStatus } from "next/link";
import { FC, ReactNode, useEffect, useState } from "react";

type LinkProps = {
  loading?: ReactNode;
  children: ReactNode;
  className?: string;
  onFinishNavigate?:()=>void;
} & OmitNextLinkProps, "prefetch" | "onNavigate">;

const Link: FCLinkProps> = ({
  children,
  loading,
  className,
  onFinishNavigate,
  ...props
}) => {
  const [isPending, setIsPending] = useStateboolean>(false);
  
  const content = isPending ? (loading ?? children) : children;

  return (
    NextLink
      prefetch={false}
      onNavigate={(e) => {
        if (isPending) {
          e.preventDefault();
        }
        
        onFinishNavigate && onFinishNavigate();
      }}
      className={cn(className, isPending && "pointer-events-none")}
      {...props}
    >
      {content}
      PendingOnChange onChange={setIsPending} />
    NextLink>
  );
};

const PendingOnChange: FC{ onChange: (isPending: boolean) => void }> = ({
  onChange,
}) => {
  const { pending } = useLinkStatus();

  useEffect(() => {
    onChange(pending);
  }, [pending, onChange]);

  return null;
};

export {
  Link,
  type LinkProps,
};

注意点

ソースコードに至らぬ箇所もあると思われます。
必要に応じて適宜直してお使いいただければ幸いです。

  • Linkのprefetchはfalseであることを想定しています。
  • tailwind cssのpointer-events-noneを使っています。
  • propsのloadingというネーミングが合わない等、修正していただければと思います。
  • 私はshadcn/uiのサイドバーを使用しているのですが、コメントアウト箇所はshadcn/uiのサイドバーに関連しています。shadcn/uiのサイドバーを使っている人はコメントアウトを復活、使ってない人はコメントアウトを行削除していただければと思います。
    何なのかというと、shadcn/uiのサイドバーでは、画面サイズがスマホくらいの横幅になると横から浮き出てサイドバーを表示できます。ただ、Linkをタップして自動でサイドバーを閉じることが出来るようにするためにはuseSidebarというhooksを使用する必要があったという事でした。他のUIライブラリでも同様の対応方法が可能と思われます。
  • onFinishNavigateというpropsを作ってますが、個人的に必要だったのをそのままにしています。不要な場合は削除いただければと思います。

ソースコード概要

useLinkStatusはとても有効なんですが、使い勝手が難しいところがあります。useLinkStatusから状態管理だけを取り出すためのコンポーネントを作成して、カスタムLinkコンポーネントに状態を持ち込んでいます。
持ち込んだ状態はisPendingにセットされています。
isPendingを使いonNavigateではロード中の連打をキャンセルしたり、pointer-events-noneでカーソルのポインタを無効にしています。
loading propsに代替のコンポーネントがなかった場合でも一応動作します。見栄えがロード中も変わらないけど、連打防止にはなるかと思います。

このコンポーネントを作れたきっかけ

このコンポーネントを作ることが出来て満足して利用できております。
そのようなものを作れたのは下記の2つのURLからでした。
一方的ですが、とても感謝しています。ありがとうございます。

  • Linkコンポーネントのカスタマイズ方法
    Linkコンポーネントのカスタマイズ方法はこのページから学びました。
    インジゲーターと組み合わせると、もっとすごいことになりそう。
    皆さんご存じだと思われますがムーザルちゃんねるのyoutubeチャンネル楽しく勉強になりますね。

https://zenn.dev/moozaru/articles/7961da23195486

  • Pending状態を持ち込む方法
    useLinkStatusの状態管理をLink本体のUIに使いたいー。pending状態を取り出す目的のコンポーネントを作っていらっしゃいます。このポストを見かけたとき、私には到底思いつかないアイデアを実践してて驚きと感動でした。実際に試してみて、結果すごい良い体験を得ることができました。正直このポストが無かったらまったく作れていませんでした。

https://x.com/kitasoft_com/status/1912161733138923559

おわりに

ほとんど他の方のアイデアを組み合わせて作ったので、記事にするのはどうなのかな、と思っておりました。
しかし私と同じ事で悩んでいらっしゃる方も少なくないはずと思い、使い勝手だけでも良くしてみて記事にさせていただいた次第です。
Linkコンポーネントでお困りの方に届きますように、どうぞ宜しくお願いいたします。

フラッグシティパートナーズ海外不動産投資セミナー 【DMM FX】入金

Source link

Views: 0

RELATED ARTICLES

返事を書く

あなたのコメントを入力してください。
ここにあなたの名前を入力してください

- Advertisment -

Most Popular