ブログ記事やニュースリリース記事等を書く際、過去に公開した記事へリンクするケースがあるかと思います。どのCMSでもリッチテキストエディタに内部リンクを記述する場合(関連記事フィールド等を用意しない場合)はa要素を使用して記述することになるだろうと考えています。そしてCMSのAPIはリッチテキストエディタに記述したHTMLを送信してきます。CMSのリッチテキストエディタ上ではNext.jsのLinkコンポーネント(next/link)を使用することはできません。(※「What is rich text? | Contentful」や「@contentful/rich-text-react-renderer」を見ると、Contentfulは違うのかもと感じています。)
結果、内部リンクをクリックしてもSPAのような振る舞いにはならず、HTML等さまざまなファイルをロードし直して描画する挙動になります。
内部リンクなのにHTMLをロードし直す挙動になると、ページの読み込みが遅く感じるだけでなく画面がちらつくような印象も受け、せっかくのNext.jsの特徴が生きないように感じます。自分では最適な解決方法が思いつかず資料を探していたのですが、数日前「reactjs - How to transform anchor links from WP API into Next.js using `dangerouslySetInnerHTML`- Stack Overflow」を見つけました。まさにぴったりの質問です。回答ではrouter.push()
を使用するコードが示されており「なるほど!」と思いました。Linkコンポーネントの実装はnode_modules/next/dist/client/link.js
だと考えますが、確かにこの中でもrouter.push()
やrouter.prefetch()
が使用されていました。
そこで、記事本文を表示する部分をEntryBody
コンポーネントとし、以下のようにuseEffect()
を使用して内部リンクをrouter
で処理するコードを記述しました。Linkコンポーネントでもmouseover
でプリフェッチをしているように見受けられたのでそれに倣いました。
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import { isLocalURL } from 'next/dist/shared/lib/router/router';
import styles from '../styles/EntryBody.module.scss';
export default function EntryBody({ content }) {
const router = useRouter();
useEffect(() => {
const links = document.querySelectorAll('.js-links a');
links.forEach(link => {
if (isLocalURL(link.href)) {
link.addEventListener('click', e => {
e.preventDefault();
router.push(link.href);
});
link.addEventListener('mouseover', () => router.prefetch(link.href));
link.addEventListener('focus', () => router.prefetch(link.href));
}
});
}, [router]);
return(
<div
className={styles['article-body'] + ' js-links'}
dangerouslySetInnerHTML={{
__html: content,
}}
/>
)
}
結果、HTMLをロードするような動作はなくなり、Linkコンポーネントを使用した時と同じ動作にすることができました。ブラウザの開発者ツールでもリクエスト数・転送量が削減できていることが確認できます。体感的にもページ遷移が速くなり大変快適です。
なお、ReactやNext.jsはまだまだ勉強中です。より良いコードが見つかった際はアップデートします。例えば「html-react-parser」でLinkコンポーネントに置き換えることができるのだろうか、などと思い始めました。「Reactで生HTMLを自由自在に加工する – KRAY Inc.」等を参考にしてさらに検討してみます。