Next.JSでつくったマークダウンブログにTwitter、Amazon、YoutubeなどのEmbedを埋め込めるようにしたのでメモです。
Embedは、ウェブページやアプリケーションに外部コンテンツを埋め込むことを指します。これにより、ユーザーが他のサイトやプラットフォームに移動することなく、そのコンテンツを直接閲覧したり操作したりすることができます。例えば、YouTubeの動画、Twitterのツイート、Google Mapsの地図などを自分のウェブサイトに直接埋め込むことができます。
Next.jsでマークダウンを実装するには、まずreact-markdown
をインストールします。これにより、マークダウン形式のテキストをHTMLに変換できます。
1npm install react-markdown 2
インストールしたら、以下のようにしてマークダウン形式のテキストをHTMLに変換できます。
1import React from 'react' 2import ReactMarkdown from 'react-markdown' 3 4 5export default function PostBody({ content }) { 6 return <ReactMarkdown>{content}</ReactMarkdown> 7} 8
ここで、content
はマークダウン形式のテキストです。これをReactMarkdown
コンポーネントに渡すことで、マークダウン形式のテキストがHTMLに変換されます。
また、react-markdown
はプラグインをサポートしているので、より高度なマークダウンの機能を利用することも可能です。例えば、GFM(GitHub Flavored Markdown)をサポートするには、remark-gfm
というプラグインをインストールします。
1npm install remark-gfm 2
インストールしたら、以下のようにしてGFMを有効にできます。
1import React from 'react' 2import ReactMarkdown from 'react-markdown' 3import gfm from 'remark-gfm' 4 5 6export default function PostBody({ content }) { 7 return <ReactMarkdown remarkPlugins={[gfm]}>{content}</ReactMarkdown> 8} 9
これで、Next.jsでマークダウンを実装することができます。
react-markdown
を利用してコードブロックをカスタマイズする方法は以下の通りです。
まず、コードハイライト用のreact-syntax-highlighter
ライブラリをインストールします。react-syntax-highlighter
は、コードに合わせてフォントカラーを変えて見読性を高めるライブラリです。VSCODEなどではおなじみですよね。
1npm install react-syntax-highlighter --save
次に、コードブロックのカスタムコンポーネントを作成します。この例では、コードブロックにシンタックスハイライトを適用します。
1'use client' 2 3 4import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; 5import { dark } from 'react-syntax-highlighter/dist/esm/styles/prism'; 6 7 8const CodeBlock = ({ inline, className, children }) => { 9 const match = /language-(\\\\w+)/.exec(className || ''); 10 return !inline && match ? ( 11 <SyntaxHighlighter style={dark} language={match[1]} PreTag="div" children={String(children).replace(/\\\\n$/, '')} /> 12 ) : ( 13 <code className={className}> 14 {children} 15 </code> 16 ) 17} 18
react-syntax-highlighterはstateを利用しているので、’use client’の指定が必要です
最後に、react-markdown
のcomponents
プロパティを用いて、code
タグに対するカスタムコンポーネントを指定します。
1<ReactMarkdown components={{ code: CodeBlock }}> 2 {markdownContent} 3</ReactMarkdown> 4
これにより、マークダウン内のコードブロックがCodeBlock
コンポーネントによりレンダリングされ、シンタックスハイライトが適用されます。
1‘‘‘記載したいコード 2hogehoge 3‘‘‘ 4
のように記載することで、classnameに
1language-記載したいコード 2
といった形で渡されるため、
1const match = /language-(\\w+)/.exec(className || '') 2
で分解します。 matchには
1 ['language-記載したいコード', '記載したいコード',・・・ 2
といった感じになるので、match[1]をSyntaxHighlighterのlangに渡し、該当の言語のハイライトを指定しています。言語の一覧については、node_modulesの中をみれば大体想像つくんじゃないかなと思います。 なお、ここまでの情報がすべてreact-markdownのGITに掲載されているので、そちらを読めばばっちりです。
1<https://github.com/remarkjs/react-markdown?tab=readme-ov-file#components> 2
1<https://github.com/react-syntax-highlighter/react-syntax-highlighter> 2
さて、ここまでが転記するソースコードに合わせてハイライトしつつマークダウンをNextJSで表示しました。今回は、TwitterやYOUTUBEなどのEmbedを表示するために、こちらのコードブロックを活用します。lang
部分にYOUTUBE
、AMAZON
、TWITTER
などを指定することによって、分岐し返却するDOMを変えることにしました。
1'use client' 2 3 4import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; 5import { dark } from 'react-syntax-highlighter/dist/esm/styles/prism'; 6 7 8const CodeBlock = ({ inline, className, children }) => { 9 const match = /language-(\\\\w+)/.exec(className || ''); 10 /* この部分でmatchの値によって、returnするDOMを分岐する */ 11 return !inline && match ? ( 12 <SyntaxHighlighter style={dark} language={match[1]} PreTag="div" children={String(children).replace(/\\\\n$/, '')} /> 13 ) : ( 14 <code className={className}> 15 {children} 16 </code> 17 ) 18} 19
ここから先は、ちょっと時間あまりかけたくなかったので、ライブラリを使用してく方向で実装してます。
X(Twitter)のembedの取得方法はいろいろとありますが、今回はあまり時間をかけたくなかったので、react-twitter-widgets
を使用しました。該当TweetのページからHTMLを生成する方法は、公式が用意しているAPIを実行する方法などがあります。
1<https://github.com/andrewsuzuki/react-twitter-widgets> 2
1<https://developer.twitter.com/en/docs/twitter-for-websites/timelines/guides/oembed-api> 2
「|」でSplitした最初の単語がTWITTERである場合は、react-twitter-widgetsを使用する感じにします。react-twitter-widgetsはtweetIdの指定が必要なので、コンテンツ部分にはTweetIdを設定します。
1‘‘‘ 2TWITTER|1732974077751488588 3‘‘‘ 4
こちらを
1return ( 2 <> 3 { 4 lang == "TWITTER" && 5 <Tweet tweetId={String(children).replace(/\\n$/, '')} /> 6 } 7 </> 8 ); 9
のような感じにしました。
リンクカードの実装についても自作でやれるとかっこいいんですが、今回ははてなブログのブログカードを利用します。
1‘‘‘ 2LINK|<https://onofblog.com/post/2023-11-26-approutervspagerouter> 3‘‘‘ 4
のようなコードブロックの場合に、「|」でSplitした最初の単語がLINKだった場合にブログカードを表示します。コンテンツはURLです。
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> 8
のようにiframeで表示し、
https://hatenablog-parts.com/embed?url=${children}
のchildrenにリンクのURLを設定します。
YOUTUBEも基本は同様です。今回はreact-youtubeを使用します。
[react-youtube(https://github.com/tjallingt/react-youtube)]
1‘‘‘ 2YOUTUBE|FDUk0Kcte9A 3‘‘‘ 4
というコードブロックだった場合、「|」でSplitした最初の単語が「YOUTUBE」だった場合にreact-youtubeを表示します。videoIDを
1<div className="youtube-wrap"> 2 <Youtube videoId={String(children).replace(/\\n$/, '')} className="w-1/2" /> 3</div> 4
上記のように指定します。
AMAZONの商品をカードで表示する最適なライブラリが見当たらなかったので、こちらについては自作しましたが正直大したことはないです。
1‘‘‘ 2AMAZON|img:https://m.media-amazon.com/images/I/71dD18FMojL._AC_SX466_.jpg|url:https://amzn.to/47ILKl1|title:ああああ> 3‘‘‘ 4
上記のようなカードを作り、「|」でSplitした最初の単語がAMAZONだった場合はimg、url、titleを分割して取得、お好みのカードに実装していくイメージになります。私はこんな感じにしてみました。
正直なところ、もう少しこだわって実装したかったところではありますが、なかなか実装スピードが上がらず楽してしまいました。ここに紹介してあるすべては、公式のAPIなどがあるのでそちらを使用したほうがスマートな気がします。最低限ブログカードは自力で実装したいところですね。
- Next.jsの記事 -