【小ネタ】Next.jsでハッシュフラグメントを張る

2022/01/23に公開
anchorlink.tsx
import { FC, useEffect, useRef } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import { UrlObject } from "url";

interface Props {
  to: UrlObject | string;
  anchor: string;
}

const AnchorLink: FC<Props> = ({ children, to, anchor }) => {
  const ref = useRef<HTMLAnchorElement>();
  const router = useRouter();
  useEffect(() => {
    const hashChanged = (url) => {
      const hash = url.split("#")[1];
      if (hash !== anchor) return;
      ref.current.scrollIntoView();
    };
    router.events.on("hashChangeStart", hashChanged);
    return () => router.events.off("hashChangeStart", hashChanged);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [anchor]);
  return (
    <Link href={to}>
      <a ref={ref}>{children}</a>
    </Link>
  );
};

export default AnchorLink;
anchorlink.tsx
import { NextPage } from "next";
import AnchorLink from ".../anchorlink"

const Index: NextPage = () => (
  <main>
    <AnchorLink to={{hash: "hoge"}} anchor={"hoge"}>
      <h1>Hoge</h1>
    </AnchorLink>
  </main>
)

export default Index;

特筆するべき点としては、Next RouterのhashChangeStartイベントを監視しアンカーとURLに含まれる#以降の値がフルマッチした時のみscrollIntoViewでスクロールするようにしています。今回はLinkを内包したコンポーネントとして定義していますが、scrollIntoViewを使用できればaタグ以外でも問題ないです。

他のテックブログでは外部ライブラリを紹介するような内容が散見されますが、このようにReact.jsとNext.jsのみで簡単に実装することができます。

Discussion