Next.jsでMarkdown->html変換時にnext/linkを利用したい
動機
- Next.jsでMarkdownブログを作成するとき、記述したサイト内リンクを
<Link>
にしたい - ついでに外部リンクは別タブにしたい
参考ページはあるけどこの通りにはいかないつまずきポイントがあった(App Routerではない、という違いだけでもない)
rehypeReactのつまずきポイント
事象
createElement
やFragment
を指定すると、
import {createElement, Fragment} from react;
unified()
.use(rehypeParse, {fragment: true})
.use(rehypeReact, {
createElement,
Fragment,
components: {
a: CustomLink,
},
})
Type errorが出る。
Type error: Argument of type '[{ createElement: { (type: "input", props?: (InputHTMLAttributes<HTMLInputElement> & ClassAttributes<HTMLInputElement>) | null | undefined, ...children: ReactNode[]): DetailedReactHTMLElement<...>; <P extends HTMLAttributes<...>, T extends HTMLElement>(type: keyof ReactHTML, props?: (ClassAttributes<...> & P) | ... ...' is not assignable to parameter of type '[boolean] | [Options]'.
Type '[{ createElement: { (type: "input", props?: (InputHTMLAttributes<HTMLInputElement> & ClassAttributes<HTMLInputElement>) | null | undefined, ...children: ReactNode[]): DetailedReactHTMLElement<...>; <P extends HTMLAttributes<...>, T extends HTMLElement>(type: keyof ReactHTML, props?: (ClassAttributes<...> & P) | ... ...' is not assignable to type '[boolean]'.
Type '{ createElement: { (type: "input", props?: (React.InputHTMLAttributes<HTMLInputElement> & React.ClassAttributes<HTMLInputElement>) | null | undefined, ...children: React.ReactNode[]): React.DetailedReactHTMLElement<...>; <P extends React.HTMLAttributes<...>, T extends HTMLElement>(type: keyof React.ReactHTML, props?...' is not assignable to type 'boolean'.
Issueがあって最新verでは大丈夫だよと書いてあるけど、
it works with latest @types/react and rehype-react in a sandbox. https://codesandbox.io/s/rehype-react-types-b5pe22?file=/src/App.tsx
上のcodesandboxも最新バージョン(rehype-parse: 9.0.0
rehype-react: 8.0.0
)にするとエラーになっちゃう
解決方法
rehype-reactのREADME見たら@ts-expect-error
で無視してた。
https://github.com/rehypejs/rehype-react#use
import * as prod from 'react/jsx-runtime'
// @ts-expect-error: the react types are missing.
const production = {createElement: prod.createElement, Fragment: prod.Fragment, jsx: prod.jsx, jsxs: prod.jsxs, }
unified()
.use(rehypeParse, {fragment: true})
.use(rehypeReact, production)
このjsx
も指定してあげないとExpected jsx in production options
というエラーになる。
けっこう時間を溶かしたが、READMEをちゃんと読みましょうということー
CustomLinkコンポーネントを定義する
next/linkはPage Router時代とちょっと仕様変わっているので注意
Link
の中にa
タグを入れない方が良い
import Link from 'next/link';
const CustomLink = ({
children,
href,
}: {
children: string;
href: string;
}): JSX.Element =>
(href.startsWith('/') || href.startsWith('#') || href === '') ? (
<Link href={href}>
{children}
</Link>
) : (
<a href={href} target="_blank" rel="noopener noreferrer">
{children}
</a>
);
export default CustomLink;