Next.JSでつくったマークダウンブログにTwitter、Amazon、YoutubeなどのEmbedを埋め込めるようにしたのでメモです。
Embedは、ウェブページやアプリケーションに外部コンテンツを埋め込むことを指します。これにより、ユーザーが他のサイトやプラットフォームに移動することなく、そのコンテンツを直接閲覧したり操作したりすることができます。例えば、YouTubeの動画、Twitterのツイート、Google Mapsの地図などを自分のウェブサイトに直接埋め込むことができます。
Next.jsでマークダウンを実装するには、まずreact-markdown
をインストールします。これにより、マークダウン形式のテキストをHTMLに変換できます。
1npm install react-markdown
インストールしたら、以下のようにしてマークダウン形式のテキストをHTMLに変換できます。
[slug]/index.tsx1import 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.tsx1import 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.tsx1'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-markdown
のcomponents
プロパティを用いて、code
タグに対するカスタムコンポーネントを指定します。
[slug]/index.tsx1<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に掲載されているので、そちらを読めばばっちりです。
さて、ここまでが転記するソースコードに合わせてハイライトしつつマークダウンをNextJSで表示しました。今回は、TwitterやYOUTUBEなどのEmbedを表示するために、こちらのコードブロックを活用します。lang
部分にYOUTUBE
、AMAZON
、TWITTER
などを指定することによって、分岐し返却するDOMを変えることにしました。
component/codeblock.tsx1'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)のembedの取得方法はいろいろとありますが、今回はあまり時間をかけたくなかったので、react-twitter-widgets
を使用しました。該当TweetのページからHTMLを生成する方法は、公式が用意しているAPIを実行する方法などがあります。
lang
にTWITTERが指定してある場合は、react-twitter-widgetsを使用する感じにします。react-twitter-widgetsはtweetIdの指定が必要なので、コンテンツ部分にはTweetIdを設定します。
1‘‘‘TWITTER 21732974077751488588 3‘‘‘
こちらを
component/codeblock.tsx1return ( 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.tsx1<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も基本は同様です。今回はreact-youtubeを使用します。
[react-youtube(https://github.com/tjallingt/react-youtube)]
1‘‘‘YOUTUBE 2FDUk0Kcte9A 3‘‘‘
というコードブロックだった場合、langがYOUTUBEだった場合にreact-youtubeを表示します。videoIDを
component/codeblock.tsx1<div className="youtube-wrap"> 2 <Youtube videoId={String(children).replace(/\n$/, '')} className="w-1/2" /> 3</div>
上記のように指定します。
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などがあるのでそちらを使用したほうがスマートな気がします。最低限ブログカードは自力で実装したいところですね。