ONOF
NEXTJSのマークダウンブログにAMZONやYOUTUBEのEmbedを表示する
2023/11/26

Next.JSでつくったマークダウンブログにTwitter、Amazon、YoutubeなどのEmbedを埋め込めるようにしたのでメモです。

Embedって

Embedは、ウェブページやアプリケーションに外部コンテンツを埋め込むことを指します。これにより、ユーザーが他のサイトやプラットフォームに移動することなく、そのコンテンツを直接閲覧したり操作したりすることができます。例えば、YouTubeの動画、Twitterのツイート、Google Mapsの地図などを自分のウェブサイトに直接埋め込むことができます。

マークダウンの対応

Next.jsでマークダウンを実装するには、まずreact-markdownをインストールします。これにより、マークダウン形式のテキストをHTMLに変換できます。

1npm install react-markdown

インストールしたら、以下のようにしてマークダウン形式のテキストをHTMLに変換できます。

[slug]/index.tsx
1import React from 'react'
2import ReactMarkdown from 'react-markdown'
3
4export default function PostBody({ content }) {
5  return <ReactMarkdown>{content}</ReactMarkdown>
6}

ここで、contentはマークダウン形式のテキストです。これをReactMarkdownコンポーネントに渡すことで、マークダウン形式のテキストがHTMLに変換されます。

また、react-markdownはプラグインをサポートしているので、より高度なマークダウンの機能を利用することも可能です。例えば、GFM(GitHub Flavored Markdown)をサポートするには、remark-gfmというプラグインをインストールします。

1npm install remark-gfm

インストールしたら、以下のようにしてGFMを有効にできます。

[slug]/index.tsx
1import React from 'react'
2import ReactMarkdown from 'react-markdown'
3import gfm from 'remark-gfm'
4
5export default function PostBody({ content }) {
6  return <ReactMarkdown remarkPlugins={[gfm]}>{content}</ReactMarkdown>
7}

これで、Next.jsでマークダウンを実装することができます。

コードブロックのハイライト

react-markdownを利用してコードブロックをカスタマイズする方法は以下の通りです。

まず、コードハイライト用のreact-syntax-highlighterライブラリをインストールします。react-syntax-highlighter は、コードに合わせてフォントカラーを変えて見読性を高めるライブラリです。VSCODEなどではおなじみですよね。

1npm install react-syntax-highlighter --save

次に、コードブロックのカスタムコンポーネントを作成します。この例では、コードブロックにシンタックスハイライトを適用します。

component/codeblock.tsx
1'use client'
2
3import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
4import { dark } from 'react-syntax-highlighter/dist/esm/styles/prism';
5
6const CodeBlock = ({ inline, className, children }) => {
7  const match = /language-(\\w+)/.exec(className || '');
8  return !inline && match ? (
9    <SyntaxHighlighter style={dark} language={match[1]} PreTag="div" children={String(children).replace(/\\n$/, '')} />
10  ) : (
11    <code className={className}>
12      {children}
13    </code>
14  )
15}

react-syntax-highlighterはstateを利用しているので、’use client’の指定が必要です

最後に、react-markdowncomponentsプロパティを用いて、codeタグに対するカスタムコンポーネントを指定します。

[slug]/index.tsx
1<ReactMarkdown components={{ code: CodeBlock }}>
2  {markdownContent}
3</ReactMarkdown>

これにより、マークダウン内のコードブロックがCodeBlockコンポーネントによりレンダリングされ、シンタックスハイライトが適用されます。

1‘‘‘記載したいコード
2hogehoge
3‘‘‘

のように記載することで、classnameに

1language-記載したいコード

といった形で渡されるため、

1const match = /language-(\w+)/.exec(className || '')

で分解します。 matchには

1 ['language-記載したいコード', '記載したいコード',・・・

といった感じになるので、match[1]をSyntaxHighlighterのlangに渡し、該当の言語のハイライトを指定しています。言語の一覧については、node_modulesの中をみれば大体想像つくんじゃないかなと思います。 なお、ここまでの情報がすべてreact-markdownのGITに掲載されているので、そちらを読めばばっちりです。

コードブロックにEmbed表示

さて、ここまでが転記するソースコードに合わせてハイライトしつつマークダウンをNextJSで表示しました。今回は、TwitterやYOUTUBEなどのEmbedを表示するために、こちらのコードブロックを活用します。lang 部分にYOUTUBEAMAZONTWITTER などを指定することによって、分岐し返却するDOMを変えることにしました。

component/codeblock.tsx
1'use client'
2
3import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
4import { dark } from 'react-syntax-highlighter/dist/esm/styles/prism';
5
6const CodeBlock = ({ inline, className, children }) => {
7  const match = /language-(\\w+)/.exec(className || '');
8  /* この部分でmatchの値によって、returnするDOMを分岐する */
9  return !inline && match ? (
10    <SyntaxHighlighter style={dark} language={match[1]} PreTag="div" children={String(children).replace(/\\n$/, '')} />
11  ) : (
12    <code className={className}>
13      {children}
14    </code>
15  )
16}

ここから先は、ちょっと時間あまりかけたくなかったので、ライブラリを使用してく方向で実装してます。

X(Twitter)

X(Twitter)のembedの取得方法はいろいろとありますが、今回はあまり時間をかけたくなかったので、react-twitter-widgets を使用しました。該当TweetのページからHTMLを生成する方法は、公式が用意しているAPIを実行する方法などがあります。

lang にTWITTERが指定してある場合は、react-twitter-widgetsを使用する感じにします。react-twitter-widgetsはtweetIdの指定が必要なので、コンテンツ部分にはTweetIdを設定します。

1‘‘‘TWITTER
21732974077751488588
3‘‘‘

こちらを

component/codeblock.tsx
1return (
2      <>
3        {
4          lang == "TWITTER" &&
5          <Tweet tweetId={String(children).replace(/\n$/, '')} />
6        }
7      </>
8  );

のような感じにしました。

ブログカード

リンクカードの実装についても自作でやれるとかっこいいんですが、今回ははてなブログのブログカードを利用します。

1‘‘‘LINK
2https://onofblog.com/post/2023-11-26-approutervspagerouter
3‘‘‘

のようなコードブロックの場合に、langがLINKだった場合にブログカードを表示します。コンテンツはURLです。

component/codeblock.tsx
1<div className="max-w-2xl">
2    <iframe
3        className="mx-auto w-full dark:opacity-80 h-56 p-4"
4        src={`https://hatenablog-parts.com/embed?url=${children}`}
5        loading="lazy"
6    />
7</div>

のようにiframeで表示し、

https://hatenablog-parts.com/embed?url=${children} のchildrenにリンクのURLを設定します。

YOUTUBE

YOUTUBEも基本は同様です。今回はreact-youtubeを使用します。

[react-youtube(https://github.com/tjallingt/react-youtube)]

1‘‘‘YOUTUBE
2FDUk0Kcte9A
3‘‘‘

というコードブロックだった場合、langがYOUTUBEだった場合にreact-youtubeを表示します。videoIDを

component/codeblock.tsx
1<div className="youtube-wrap">
2  <Youtube videoId={String(children).replace(/\n$/, '')} className="w-1/2" />
3</div>

上記のように指定します。

AMAZON

AMAZONの商品をカードで表示する最適なライブラリが見当たらなかったので、こちらについては自作しましたが正直大したことはないです。

1‘‘‘AMAZON
2img:https://m.media-amazon.com/images/I/71dD18FMojL._AC_SX466_.jpg|url:https://amzn.to/47ILKl1|title:ああああ
3‘‘‘

上記のようなカードを作り、|でsplit、langがAMAZONだった場合はimg、url、titleを分割して取得、お好みのカードに実装していくイメージになります。私はこんな感じにしてみました。

ああああ

まとめ

正直なところ、もう少しこだわって実装したかったところではありますが、なかなか実装スピードが上がらず楽してしまいました。ここに紹介してあるすべては、公式のAPIなどがあるのでそちらを使用したほうがスマートな気がします。最低限ブログカードは自力で実装したいところですね。