Cover Image for ブログ内のリンクを Next.js の <Link> や Embed に変換したい

ブログ内のリンクを Next.js の <Link> や Embed に変換したい

概要

このブログ内のリンクを Next.js の <Link> や Twitter の Embed に変換したいので対応した

結論

  • rehype-react を使ってReactコンポーネントと置き換える
  • Twitter埋め込みはreact-twitter-embedを使う

埋め込むとこんな感じ

方法

Markdownをrehypeで読み込んだものを置き換える

rehype-react を導入

$ yarn add rehype-react

Markdown -> HTML 変換したものをさらに Reactコンポーネントに変換する

dangerouslySetInnerHTML に流し込んでいるだけの部分を以下のように変更して、Reactにさらなる変換をかける

// post-body.tsx
+import { processor } from '../../../lib/htmlToReact'
import markdownStyles from './markdown-styles.module.css'  
  
type Props = {  
  content: string  
}  
  
const PostBody = ({ content }: Props) => {  
  return (  
    <div className="max-w-2xl mx-auto">  
-      <div className={markdownStyles['markdown']}  
-        dangerouslySetInnerHTML={{ __html: content }}  
-      />  
+	   <div className={markdownStyles['markdown']}>  
+	     {processor.processSync(content).result}  
+	   </div>
	</div>  )  
}  
  
export default PostBody

Reactに変換する部分の本体は以下のような感じ fragment: true にしておかないとheadとかbodyが挿入されてしまうので、必ずtrueにしておくこと

// htmlToReact.ts
import { createElement } from 'react'  
import rehypeParse from 'rehype-parse'  
import rehypeReact from 'rehype-react'  
import { unified } from 'unified'  
  
import CustomLink from '../components/headless/custom-link'  
  
export const processor = unified()  
  .use(rehypeParse, { fragment: true }) // fragmentは必ずtrueにする  
  .use(rehypeReact, { 
    createElement
  })

a-tagを独自コンポーネントに入れ替える

react-twitter-embed の導入

Twitterのembedは普通にやるとうまくいかないので react-twitter-embed を使う

$ yarn add rehype-react

独自コンポーネントへの入れ替え

rehypeReact でどのタグをどのコンポーネントに変換するかを設定する

// htmlToReact.ts
import { createElement } from 'react'  
import rehypeParse from 'rehype-parse'  
import rehypeReact from 'rehype-react'  
import { unified } from 'unified'  
  
+import CustomLink from '../components/headless/custom-link'  
  
export const processor = unified()  
  .use(rehypeParse, { fragment: true }) // fragmentは必ずtrueにする  
  .use(rehypeReact, { 
    createElement,  
+	components: {  
+	  a: CustomLink  
+	}
  })

変換するコンポーネントはこんな感じ。 必要に応じてLinkに変更したり、embedしたりする。

import Link from 'next/link'  
import { FC, VFC } from 'react'  
import { TwitterTweetEmbed } from 'react-twitter-embed'  
  
type Props = {  
  href: string  
}  
  
const Embed: VFC<Props> = ({ href }) => {  
  if (href.includes('https://twitter.com/')) {  
    const url = new URL(href)  
    const paths = url.pathname.split('/')  
    const tweetId = paths[paths.length - 1]  
    return <TwitterTweetEmbed tweetId={tweetId} />  
  }  
  return (  
    <a href={href} target="_blank" rel="noopener noreferrer">  
      {href}  
    </a>  
  )  
}  
  
const CustomLink: FC<Props> = ({ children, href }) => {  
  if (children === undefined || children === null || children === '') {  
    // a-tagの中身が空ならembedに変換する  
    return <Embed href={href} />  
  }  
  
  // a-tagに中身がある場合は必要に応じてLinkに変換する  
  return href.startsWith('/') || href === '' ? (  
    <Link href={href}>  
      <a>{children}</a>  
    </Link>  ) : (  
    <a href={href} target="_blank" rel="noopener noreferrer">  
      {children}  
    </a>  
  )  
}  
  
export default CustomLink

参考