りゅーそう
元高校地歴科教員。現在microCMSでエンジニアをしています。
Saitama.jsというLT会を運営中です。
発言はすべて個人の意見です。
blog
microcms
nextjs
2020/02/28
2022/01/23
当ブログは以前の記事りゅーそうブログを支える技術でも書いたように、Next.js × microCMS の Jamstack 構成で作成されています。
今回はNext.jsとmicroCMSを使って、Markdownで書く技術ブログの始め方を解説していこうと思います。
React/Next.jsを触ったことがある。以前に書いた記事をお勧めします。当記事もTypeScriptを使用します。またNext.jsTutorialはやりがいもあって面白いので触れたことがない方にお勧めしておきます。
また当記事に使用するライブラリは以下の通りです。
では初めていきましょう。
はじめにmicroCMSでコンテンツを作成していきます。
以下のようなスキーマのAPIを作成してください。microCMSのアカウント登録・スキーマの作成方法についてはmicroCMS Blogをご覧ください。
タグ
ブログ
contentはテキストエリアを選択してください。リッチエディタは文字の装飾・画像の装飾などとても便利ですが今回はMarkdownを用いて書いて行くのでテキストエリアを選択します。
以上のようなスキーマを作成できたら、いくつかコンテンツを作成しておきましょう。
yarnを使います。npmを使用している方は、適宜npmに書き換えてお読みください。
yarn add next react react-dom react-high-right axios dotenv marked
type定義のファイルなどをインストール
yarn add -D @types/node @types/react @types/react-dom @types/react-highlight @types/marked typescript
コードの解説は上記の記事をご覧ください。
[src/pages/index.tsx]
import * as React from 'react';
import { NextPage } from 'next';
import Link from 'next/link';
import { axiosInstance } from '../../lib/api';
import { Post } from '../../types';
type Props = {
posts: Post[];
};
const BlogsPage: NextPage<Props> = ({ posts }) => {
return (
<>
<h2>BLOG 一覧</h2>
<div>
{posts.map(post => (
<React.Fragment key={post.id}>
<Link href={`posts/${post.id}`}>
<a>
<h2>{post.title}</h2>
</a>
</Link>
{post.tags.map(tag => (
<React.Fragment key={tag.id}>
<span>{tag.name}</span>
</React.Fragment>
))}
</React.Fragment>
))}
</div>
</>
);
};
PostsPage.getInitialProps = async () => {
const res = await axiosInstance.get(
`https://ryusou-mtkh.microcms.io/api/v1/posts/`,
);
const data: Post[] = await res.data.contents;
return { posts: data };
};
export default PostPage;
[src/pages/[id].tsx]
import * as React from 'react';
import { NextPage } from 'next';
import { axiosInstance } from '../../lib/api';
import { IPost } from '../../interfaces';
type Props = {
post: IPost;
};
const PostContent: NextPage<Props> = ({ post }) => {
return (
<>
<h1>{post.title}</h1>
<div>
{post.tags.map(tag => (
<React.Fragment key={tag.id}>
<span>{tag.name}</span>
</React.Fragment>
))}
</div>
<img src={post.image.url} />
<div dangerouslySetInnerHTML={{ __html: `${post.content}` }}></div>
</>
);
};
PostContent.getInitialProps = async context => {
const { id } = context.query;
const res = await axiosInstance.get(
`https://ryusou-mtkh.microcms.io/api/v1/posts/${id}`,
);
const post: Post = await res.data;
return { post };
};
export default PostContent;
ここからが本題です。markedjsというライブラリを使って、MarkdownをHTMLに変換して出力することで技術ブログを作成していきます。
こちらのサイトがお勧めです。
Markdown書き方マニュアル
Qiitaの文章の書き方をイメージすると馴染みやすいですね。
MarkdownをHTMLに変換するライブラリ。ドキュメントは以下の通りです。
Marked.js Documentation
使い方は簡単です。
以下のように、
import marked from 'marked'
const content = `# Marked!! content!!`
const markedContent = marked(content);
console.log(merkedContent);
// <h1>Merked!!</h1>
//<p>content!!</p>
変換したい要素をmarkedで囲ってあげるだけなので簡単ですね。
また、markedには設定をすることもできます。
marked.setOptions({
gfm: true,
breaks: true,
silent: false,
});
gfmとはGitHubが拡張したMarkdownのルールです。breaksを設定することによって改行を簡単にすることができます。設定可能なOptionについては以下をご覧ください。
Options
gfmとは
Markdownは以下のように記述することによって、コード例を示すことができます。
```javascript
const hoge = hogehoge;
console.log(hoge);
```
(※正しく```は半角で入力します。説明のため全角で表示しています。)
Highlight.jsはMarkdownのこのコード部分を読み取って、シンタックスハイライトを付与することができるライブラリです。
highlight.js
これを使って、marked.jsに設定を書くこともできるのですが、
Reactのライブラリ、react-hightが便利なので、今回はこれを用いて作成したいと思います。
では、先ほど作成した個別記事のページにこのmarked.jsの設定とhighlight.jsを適用させてみましょう。
[src/pages/[id].tsx]
import * as React from 'react';
import { NextPage } from 'next';
import { axiosInstance } from '../../lib/api';
import { IPost } from '../../interfaces';
//ライブラリのインポート
import marked from 'marked'
import Highlight from 'react-highlight';
type Props = {
post: IPost;
};
//markedのoptionを設定
marked.setOptions({
gfm: true,
breaks: true,
silent: false,
});
const PostContent: NextPage<Props> = ({ post }) => {
return (
<>
<h1>{post.title}</h1>
<div>
{post.tags.map(tag => (
<React.Fragment key={tag.id}>
<span>{tag.name}</span>
</React.Fragment>
))}
</div>
<img src={post.image.url} />
//react-highlightとmarked.jsで変換したいcontentに設定を行う
<Highlight innerHTML={true}>{marked(post.content)}</Highlight>
</>
);
};
PostContent.getInitialProps = async context => {
const { id } = context.query;
const res = await axiosInstance.get(
`https://ryusou-mtkh.microcms.io/api/v1/posts/${id}`,
);
const post: Post = await res.data;
return { post };
};
export default PostContent;
react-highlightはinnerHTML={true}を設定することによって、dangerouslySetInnerHTMLのようにHTMLを埋め込むことができます。これでMarkdownで作成したコンテンツをHTMLに変換することができました!
Next.jsは_app.jsを作成することによって、全ページにデフォルトの設定を行い、ルーティングもされない特別なページを作成することができます。
[src/pages/_app.tsx]
import React from 'react';
import Document, {
Head,
Main,
NextScript,
DocumentContext,
} from 'next/document';
export default class MyDocument extends Document {
static getInitialProps(ctx: DocumentContext) {
return Document.getInitialProps(ctx);
}
render() {
return (
<html>
<Head lang="ja">
<meta charSet="utf-8" />
<meta
name="viewport"
content="initial-scale=1.0, width=device-width"
/>
// hightlight.jsのテーマを設定する
<link
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.10.0/styles/atom-one-light.min.css"
rel="stylesheet"
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</html>
);
}
}
上記のようにテーマをHeadに設定して適用させましょう。例では、CDNでインストールしていますが、node_modules/highlight.js内にテーマがあるのでそのファイルを指定することによって好きなテーマを適用させることができるようになります。
以上でMarkdownをHTMLに変換し、コードにハイライトをつけてみやすくすることができました!技術ブログの完成ですね!
以上で最低限のMarkdownで作成する技術ブログの完成です。ここからはOptionになりますが、Markdownから生成したHTMLにクラスを付与してレイアウトを整えたいといった場合があると思います。
当ブログでもReactのUIライブラリであるChakraUIを使用し、ResetCSSを適用させているためHTMLを生成しただけではタグのfontSizeが拡大されないなどの不具合が出てきてしまいます。
marked.jsではこのような場合、redererを使って拡張することができます。やり方を解説します。
libにmarkedの処理をまとめます。
先ほどのOptionもこのファイルにまとめました。
[src/lib/marked.ts]
```javascript
import marked from 'marked';
export const markedOption = marked.setOptions({
gfm: true,
breaks: true,
silent: false,
});
//rendererを行う関数を実装する
export const markedRender = function() {
//rendererの初期化
const renderer = new marked.Renderer();
//renderer.headingでh1,h2,h3...要素を取得し、クラスを付与する。
renderer.heading = function(text, level) {
const escapedText = text.toLowerCase().replace(/[^\w]+/g, '-');
return `
<h${level} class="author" href="#${escapedText}">
${text}
</h${level}>
`;
};
//linkや他の要素も同様
renderer.link = function(href, title, text) {
return `
<a class="contentLink" href=${href} title=${title}>${text}</a>
`;
};
renderer.table = function(header, body) {
return `
<table class="contentTable">
<thead class="contentThead">${header}</thead>
<tbody class="contentTbody">${body}</tbody>
</table>
`;
};
renderer.paragraph = function(text) {
return `
<p class="paragraph">${text}</p>
`;
};
//最後にまとめてrendererを返す
return renderer;
};
上記のようにrenderer.headingのようにすることによってMarkdownで出力される各要素を取得し、処理を拡張することができます。今回は単純に要素にクラスを付与する処理を行いました。
取得できる要素については以下のDocumentをご覧ください。
Extending Marked
このexportされた関数をComponentに実際に適用させます。
[src/pages/[id].tsx]
import * as React from 'react';
import { NextPage } from 'next';
import { axiosInstance } from '../../lib/api';
import { IPost } from '../../interfaces';
//インポートする
import { markedOption, markedRender } from '../../lib/marked';
import Highlight from 'react-highlight';
type Props = {
post: IPost;
};
const PostContent: NextPage<Props> = ({ post }) => {
return (
<>
<h1>{post.title}</h1>
<div>
{post.tags.map(tag => (
<React.Fragment key={tag.id}>
<span>{tag.name}</span>
</React.Fragment>
))}
</div>
<img src={post.image.url} />
//インポートしたmarkedの設定を利用して拡張する
<Highlight innerHTML={true}>{markedOption(post.content, {renderer: markedRender())}</Highlight>
</>
);
};
PostContent.getInitialProps = async context => {
const { id } = context.query;
const res = await axiosInstance.get(
`https://ryusou-mtkh.microcms.io/api/v1/posts/${id}`,
);
const post: Post = await res.data;
return { post };
};
export default PostContent;
あとはよしなに、CSSを書くなりしてスタイルを整えたり、自由にカスタマイズしてください!
以上になります。
microCMSを使って、Markdownで書く技術ブログを作成できました!拡張性がとても高いのがJamstackの魅力だと思うので、ぜひ色々拡張してみて、自身のブログを作成してみてください!
今回のコードをみたい方は以下のリポジトリをご覧ください!
GitHub
解決策として、