マークダウンから変換したhtmlのaタグをNext.jsのLinkタグに変換する
自分用のメモです。
Next.jsのチュートリアルではマークダウンファイルを自動でhtmlに変換していますが、その変換したhtmlの中でnext/link
を使いたい、と思ったのがきっかけです。
端的に言うと「rehype-react
のオプションでa
を自作コンポーネントに置換する」だけです。
ただ、この方法が良いのかどうかはわかりません。
マークダウンの中でjsx記法が使えるmdxを使うのが一番良いのかな~。
コード
HtmlToReact.ts
import React from "react";
import unified from "unified";
import rehypeParse from "rehype-parse";
import rehypeReact from "rehype-react";
import MarkdownLink from "@/components/blog/MarkdownLink";
const HtmlToReact = (content: string) => {
const result = unified()
.use(rehypeParse, { fragment: true }) // head bodyを出力しない
.use(rehypeReact, {
createElement: React.createElement,
components: { a: MarkdownLink },
})
.processSync(content).result;
return result;
};
export default HtmlToReact;
やってることは単純で、htmlをReactElementに変換する際に、rehype-react
のオプションにあるcomponents:
でa
を自分で作ったコンポーネントMarkdownLink
に置き換えているだけです。
HtmlToReact
では、htmlの文字列を引数とします。Next.jsのチュートリアルで、remark()
を使って出力するhtmlが使えると思います。
読み込んだhtml文字列はunified
で変換します。nextjsのチュートリアルで使うremark()
と、基本は同じ使い方をするプラグインです。rehype-parse
で文字列を構文木にパースして
rehype-react
でReactElementに変換しています。
MarkdownLink.tsx
import React from "react";
import Link from "next/link";
type Props = {
children: React.ReactNode;
href: string;
};
const MarkdownLink = ({ children, href }: Props) => {
if (href.startsWith("http")) {
return (
<a href={ href } target="_blank" rel="noopener noreferrer">
{ children }
</a>
);
}
else {
return (
<Link href={ href }>
<a>{ children }</a>
</Link>
);
}
};
export default MarkdownLink;
こちらはa
の代わりに使うリンクです。
外部リンクは普通のa
タグを使っています。外部リンクと判別するための条件は「http」で始まるリンクをとしていますが、これは記事を書く際の運用で決めれば良いと思います。
httpで始まらない場合は内部リンクとみなして<Link>
を使う形です。
注意点は、外部リンクでtarget="_blank"
を使っているので、脆弱性対策のためにrel="noopener noreferrer"
を入れているところです。
使用するプラグイン
next.jsのチュートリアルでも使われているremark-html
などと同類のものです。
いろんな所で使われているようです。気づかないうちにunified使用のサービスを自分も使っている、なんてことも多そうですね。
まとめと、解決できていない問題について
苦肉の策、といった感じですが、htmlにした文字列をさらにReactElementに再変換することで強引に実現しています。
マークダウンファイルを変換してブログ記事を作る際に、自動で変換できるのはとても便利ですが、個別にcssを当てたり、コンポーネントを当てたりするにはどうしたらいいの? と素人としては結構困りました。
Next.jsのチュートリアルの後、自分が思うブログを作ろうと思ったときに、この「マークダウンから変換してきた記事」の扱いでかなり躓きまして(今でも躓いて起き上がれてない部分もありますが)、そのうちの解決策の一つがrehype-react
でした。
冒頭で書きましたが、最初からmdxを使うのが良いのかな~というのが、これを作った後に色々調べて思いついた解決策です。
解決できていない問題
<a>
を<Link>
に変換する件に関しては、これで動いているので良いのですが、Next.jsで自分が解決できていない問題がありまして。
Next.jsで動的ルーティングをする際の<Link>
とuseEffect
についてです。
Next.jsでは、pageフォルダ配下で[id].tsxみたいな感じで動的ルーティングをすることができます。この[id]
内で<Link>
を使って別の[id]
のページに飛ぶと[id]
で使用するuseEffect
のclean upが動作しないのです。まあ仕組み的には動作しないのが正しいのですよね、多分。useEffect
のcleanupはアンマウントしたときに動作するわけなので。
router.events
のrouteChangeComplete
とかrouteChangeStart
とか使えばなんとかなるのかなと色々やってみたのですが、上手くいかず…。
なので、結局[id]
内で[id]
にnext/link
でリンクを貼るのは諦めました。
とは言っても[id]
内でuseEffect
を使っていなければ問題は無さそうなので、前の記事・次の記事のリンクなどに使えます。
Discussion