こんにちは野崎です。
先日「繰り返しフィールド・カスタムフィールドをマスターしよう」という記事を書かせていただきました。
https://blog.microcms.io/intro-custom-repeatfield/
そこで様々な形式のコンテンツを扱う方法を紹介させていただきました。
microCMSはAPIベースのヘッドレスCMSです。
画面上でスタイルをつけるにはパース処理をしたり、CSSを書いたりなどして加工・装飾する必要があります。
当記事では、前回の記事を題材にコンテンツをパースする選択肢を紹介します。
要約
- Jamstack構成にして、コンテンツを事前生成するようのがベストプラクティス
- サイトの規模や要件によってはフロントエンドでパース処理を行うことも検討する
- スタイルを適用するという目的だけならCSSを当てるだけでもOKなケースも多い
コンテンツをパースする方法
それでは具体的に紹介しようと思います。
サーバーサイドで処理を行う
Jamstackの特徴の一つとして「Pre-Rendring」があげられます。
従来のSSRなどとは異なり、ビルド時にコンテンツを事前取得し、ページを生成します。
サーバーサイド側でパース処理を行うことで、コンテンツを事前に加工することができパフォーマンスの向上が期待できます。
Next.jsやNuxt.jsなどを使用するとサーバーサイド側の処理を簡単に記述することができます。
Next.jsではgetStaticPropsを利用して以下のようにシンタックスハイライトなどの処理を行うことができます。
パースを行うライブラリcheerioを使用しています。
cheerioはNode.js(サーバーサイド)上でHTMLをパースするライブラリです。詳しくはドキュメントをご覧ください。
https://github.com/cheeriojs/cheerio
type Props = {
blog: BlogType;
content: string;
};
export const getStaticProps: GetStaticProps = async (
context: GetStaticPropsContext<{ id: string }>,
) => {
const { id } = context.params;
const blog = await getBlog(id);
const { content } = blog;
const $ = cheerio.load(content);
$('pre code').each((_, element) => {
const result = hljs.highlightAuto($(element).text());
$(element).html(result.value);
$(element).addClass('hljs');
});
$('img').each((_, element) => {
$(element).html();
$(element).addClass(`${styles.blogContentImg}`);
});
$('a').each((_, element) => {
$(element).html();
$(element).addClass(`${styles.blogContentLink}`);
});
$('h1').each((_, element) => {
$(element).html();
$(element).addClass(`${styles.heading1}`);
});
return {
props: {
blog,
content: $.html(),
},
};
};
export const getStaticPaths = async () => {
const { blogs } = await getBlogList();
const paths = blogs.contents.map((content) => `/blogs/${content.id}`);
return { paths, fallback: false };
};
(コード全容はこちらをご参考ください)
サーバーサイド側(Next.jsでは getStaticProps
がそれに当たります。)で処理を行い、Jamstack構成を取ることでパフォーマンスの向上が期待できます。
またクライアント側にパース処理が露出しないのでセキュリティも高まります。
Nuxt.jsの例はこちらを参考にしてみてください
https://blog.microcms.io/syntax-highlighting-on-server-side/
クライアントサイドで処理を行う
SSRの構成を取る場合やAPIのスキーマが複雑な場合、クライアントサイドのパースライブラリを活用したいケースなどはクライアントサイドでの処理も検討すると良いと思います。
パフォーマンスではJamstackに劣るケースもありますが、その分ライブラリを活用することにより処理を簡単に行えます。
前回の記事のmicroCMSのテキストエリアのマークダウン記法を読み込んでパースする例を紹介します。
マークダウンをパースするライブラリは多くありますが、ここでは react-markdown
を使用しました。
実装は react-markdown
のシンタックスハイライトの例を参考にしました。
https://github.com/remarkjs/react-markdown#use-custom-components-syntax-highlight
import ReactMarkdown from 'react-markdown';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { tomorrow } from 'react-syntax-highlighter/dist/cjs/styles/prism';
type Props = {
text: string;
};
// propsのtextにはAPIのテキストフィールドの値を入れます。
export const MarkdownField: React.VFC<Props> = ({ text }) => {
return (
<ReactMarkdown
children={text}
components={{
code({
node,
inline,
className,
children,
...props
}) {
const match = /language-(\w+)/.exec(className || '');
return !inline && match ? (
<SyntaxHighlighter
children={String(children).replace(/\n$/, '')}
style={tomorrow}
language={match[1]}
PreTag="div"
{...props}
/>
) : (
<code className={className} {...props}>
{children}
</code>
);
},
}}
/>
);
};
このようにシンタックスハイライトを細かくカスタマイズしたいなどの要件がある場合は、クライアントサイドのライブラリを活用するのも選択肢の一つです。
クライアントサイドでHTMLをパースしたいという場合には、先程紹介したcheerioはNode.js上(Next.jsだとgetStaticProps、getServerSideProps内の処理がそれにあたります)でしか動作しないため、その他のライブラリを使用する必要があります。
クライアントサイドでHTMLをパースするには unified
関連のライブラリが便利です。
unifiedとはざっくりまとめると「構文木(自然言語を名詞や動詞の構文で解析して木構造にしたもの)を使用して様々なテキストを処理するためのインターフェース」です。
構文木から文法のための記号などの情報を除いたものをASTと言います。
コーティングをするとき様々なタグを使用しますが、実際にブラウザに表示される時はタグは表示されません。それをイメージしていただければと思います。
例)<h1>見出し</h1>
→ 見出し
詳しくはドキュメントをご覧ください。
https://unifiedjs.com/
ただ、実際に基本的な処理をするときはこれらの知識がなくても問題はありません。
HTMLをパースするには rehype
というライブラリを使用します。rehype-parse
というunifiedのライブラリを使うことによって、受け取ったテキストを解釈してASTを生成することができます。
Reactでrehypeを使用する際には rehype-react
を使用することによって、さらにReactDOMが解釈できるようにパース処理を行うことができます。
これらの処理をまとめると以下のようになります。
import { Fragment, createElement } from 'react';
import { unified } from 'unified';
import parse from 'rehype-parse';
import rehypeReact from 'rehype-react';
import styles from './paragraph.module.scss';
type Props = {
children: React.ReactNode;
};
export const Paragraph: React.VFC<Props> = ({ children }) => {
return <p className={styles.main}>{children}</p>;
};
export const parseHtml = (content: string) => {
const htmlAst = unified()
.use(parse, { fragment: true })
.use(rehypeReact, {
createElement,
Fragment,
components: {
p: Paragraph,
},
})
.processSync(content).result;
return htmlAst;
};
クライアントでパースを行うことで、様々なユースケースに対応できるケースも広がります。
またパフォーマンス面でもCDNなどを活用することによって対応する事例もあるようです。
詳しくは、microCMSアドベントカレンダーやジャムジャム!!Jamstackの過去のアーカイブなどをご覧いただくと参考になるかと思います。
microCMS Advent Calender 2021
https://qiita.com/advent-calendar/2021/microcms
ジャムジャム!!Jamstackアーカイブ
https://www.youtube.com/channel/UCjdOq0rAB8Or1uHio9N0_pQ/videos
パース処理を行わないという選択肢
ここまで、どのようにパース処理を行うという話をさせていただきました。
しかしスタイルを当てたいだけの場合など、パース処理をしなくても良いケースも存在します。
Next.js(React)の場合、dangerouslySetInnerHTML
を使用します。ドキュメントの通り正しく使用してください。
https://ja.reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
<div key={index} className={styles.className}>
<div dangerouslySetInnerHTML={{ __html: body.richText }} />
</div>
CSSをラップしたclassNameに適用します。
.className {
//スタイルを当てたい要素にCSSを指定する
h1 {
color: red;
}
iframe {
height: auto;
width: 100%;
aspect-ratio: 16 / 9;
}
}
加工の柔軟度はなくなりますが、スタイルを適用するのが簡単になるかと思います。
とりあえずスタイルを当てたいというケースやパースしきれない要素などがある場合はこの方法も基本的ではありますが、知っておくと良いと思います。
最後に
この記事ではコンテンツをパースする選択肢を紹介させていただきました。
実務においては、要件やスケジュールなどに応じて方法を選択できると良いかと思います。他にも良い方法がありましたらぜひSNSやイベントなどでシェアしてください!
-----
microCMSは日々改善を進めています。
ご意見・ご要望は管理画面右下のチャット、公式Twitter、お問い合わせからお気軽にご連絡ください!
引き続きmicroCMSをよろしくお願いいたします!