next.js+microcmsでシンタックスハイライトの導入

# はじめに
microcmsとJavaScriptフレームワークを使ったJAMSTACKブログの記事はめちゃめちゃあります。

でも..........

### microcmsのリッチテキストを使ったシンタックスハイライト実装に関する記事が全くない😂

しかもnext.js microcmsでググると

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/660329/8e01bb15-73a3-4ec3-600e-cc59b3f5b995.png)

### oh.......

Nuxtの記事が多すぎて「もしかして：」が出てしまう。。。。。

なので記事にしました。

### シンタックスハイライトとは

こんな感じでコードに色を持たせて強調するものです。
とてもみやすくなりますし、技術ブログではほぼほぼ必須です。

```tsx
const hoge = 'hogehoge'

const fuga = 'fugafuga'
```


# 実装方法

大体この記事を見ている人は他の方のブログ実装の記事を見ていてある程度は一緒のディレクトリ構成になっていると思います。なので1から作りたい人は以下の記事を参考にしながら途中まで実装してみてください。

[Next.js + microCMS + NetlifyでJAMstackな世界に入門する](https://qiita.com/hiro08gh/items/07bd5fc2a93105dfe180)
[Vercel + Next.js + micro CMSで作る、jamstackなCMS構築](https://qiita.com/isihigameKoudai/items/a76165f1239614d15aab)

実装するにあたって参考にしたのは
microcmsのこちらの記事です。

[サーバサイドでシンタックスハイライトを行う](https://blog.microcms.io/syntax-highlighting-on-server-side/)

こちらの記事はNuxtでの実装方法が書かれていますがNextに転用して使います。

記事の中でこのように書かれています。

> microCMSではリッチエディタでソースコードの入力を行う場合、以下のようなHTMLが出力されます。

```
<pre>
  <code>
    // ソースコード
  </code>
</pre>
```

リッチエディタで作成されたデータをjson形式で取り出す時には以下のように取り出されます。
(1行で申し訳ない)

```json
blog:{
"body":"<pre><code>const fruits = [&#x27;apple&#x27;,&#x27;grapes&#x27;,&#x27;pomme&#x27;]\n\nconst cake = fruits.map((fruit:string) =&gt; {\n  fruit + &#x27;cake&#x27;\n})\n\nconsole.log(cake)</code></pre><p><br>これはどうだい？<br><code>hogehoge</code></p><h1 id=\"ha53b8ea4b7\">ほい！</h1><p><br><span style=\"font-size: 0.75em\">hogehoge</span><br><strong style=\"font-size: 0.75em\">ううおお</strong><br><br><img src=\"https://images.microcms-assets.io/assets/c2ccfec8448e4d2abdaad5969c2ba309/1d60bef563904e789c4891d7644a0305/kumon.jpg\" alt><br><br></p><blockquote>こんにちは</blockquote><p><br></p>"
}
```

ただこのまま

```tsx
<div dangerouslySetInnerHTML={{ __html: blog.body }}></div>
```

としてしまうと

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/660329/5ed0a341-9f8c-8965-4038-3855938fe3df.png)
(実際の見た目をスクリーンショットしたもの)

こんな感じで味気なくなってしまいます。

ここで,microcms公式の

>
以下のライブラリを使います。
cheerio,highlight.js
cheerioはHTMLパーサーで、highlight.jsはシンタックスハイライト用のライブラリです。
highlight.jsには様々なAPIが用意されています。
今回は `highlightAuto()` を使います。
`highlightAuto()` は引数にソースコードの文字列を与えると、自動で言語を推測して良い感じにハイライトしてくれます。
microCMSではリッチエディタでソースコードの入力を行う場合、以下のようなHTMLが出力されます。

```
<pre>
  <code>
    // ソースコード
  </code>
</pre>
```

cheerioを使って該当のソースコード部分を抜き出し、そこにハイライトを当てるには次のように記述します。

```tsx
import cheerio from 'cheerio';
import hljs from 'highlight.js'

// 略

const $ = cheerio.load(data.body);    // data.bodyはmicroCMSから返されるリッチエディタ部分
$('pre code').each((_, elm) => {
  const result = hljs.highlightAuto($(elm).text());
  $(elm).html(result.value);
  $(elm).addClass('hljs');
});

console.log($.html());    // ハイライト済みのHTML
```

まずこのcheerioとhljsをインストールした後に
`pages/{ページディレクトリ}/[id].tsx`
例)pages/blogs/[id].tsx

に上記のコードを追記します。

またこの

```tsx
const $ = cheerio.load(data.body);    // data.bodyはmicroCMSから返されるリッチエディタ部分
$('pre code').each((_, elm) => {
  const result = hljs.highlightAuto($(elm).text());
  $(elm).html(result.value);
  $(elm).addClass('hljs');
});
```
を記述する場所なのですがこちらは`getStaticPropsのなかに書いていきます。`

```tsx
export const getStaticProps = async (context: any) => {
  const id = context.params.id

  const key: any = {
    headers: { 'X-API-KEY': process.env.API_KEY },
  }

  const res = await fetch(
    `https://hogehoge.microcms.io/api/v1/blogs/${id}`,
    key,
  )
  const blog = await res.json()
  const $ = cheerio.load(blog.body)

  $('pre code').each((_, elm) => {
    const result = hljs.highlightAuto($(elm).text())
    $(elm).html(result.value)
    $(elm).addClass('hljs')
  })
  return {
    props: {
      blog,
      highlightedBody:$.html()
    },
  }
}
```

> getStaticPropsはgetInitialPropsが行っていた処理をビルド時に行い、静的なファイルを事前に生成するためのAPIです。next exportと異なりnext linkでルーティングした場合でも静的なファイルを利用します。
このメソッドはクライアント側で実行されることはありません。必ず事前にサーバーサイドで実行されます。

引用:[Next.js 9.3新API getStaticProps と getStaticPaths と getServerSideProps の概要解説](https://qiita.com/matamatanot/items/1735984f40540b8bdf91#getstaticprops)

これをすることで事前にサーバーサイドレンダリングが行われ、propsとして渡されます
`highlightedBody:$.html()`とすることで記事本体にシンタックスハイライトがかかったhtmlコードがpropsとして渡されます。

```tsx
return {
    props: {
      blog,
      highlightedBody:$.html()
    },
```

またこの$.html()を出力してみると

```
$.html(は"<html><head></head><body><pre><code class=\"hljs\"><span class=\"hljs-keyword\">const</span> fruits = [<span class=\"hljs-string\">'apple'</span>,<span class=\"hljs-string\">'grapes'</span>,<span class=\"hljs-string\">'pomme'</span>]\n\n<span class=\"hljs-keyword\">const</span> cake = fruits.map(<span class=\"hljs-function\">(<span class=\"hljs-params\">fruit:<span class=\"hljs-built_in\">string</span></span>) =&gt;</span> {\n  fruit + <span class=\"hljs-string\">'cake'</span>\n})\n\n<span class=\"hljs-built_in\">console</span>.log(cake)</code></pre><p><br>これはどうだい？<br><code>hogehoge</code></p><h1 id=\"ha53b8ea4b7\">ほい！</h1><p><br><span style=\"font-size: 0.75em\">hogehoge</span><br><strong style=\"font-size: 0.75em\">ううおお</strong><br><br><img src=\"https://images.microcms-assets.io/assets/c2ccfec8448e4d2abdaad5969c2ba309/1d60bef563904e789c4891d7644a0305/kumon.jpg\" alt=\"\"><br><br></p><blockquote>こんにちは</blockquote><p><br></p></body></html>"
```

こんな感じで加工されたhtmlが返されます。

これを

```tsx
<div dangerouslySetInnerHTML={{ __html: highlightedBody }}></div>
```
として渡すと、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/660329/ab39d35d-0c2f-46e7-b559-94cd8bbfbf1c.png)
こんな感じでいい感じで色がついてみやすくなりました
ちなみにテーマは
night-owlを使っています
テーマの使い方は

```
import 'highlight.js/styles/night-owl.css';
```

コード冒頭でimportすることで切り替えてくれます。

コード全体
page/blogs/[id].tsx

```tsx
import React from 'react';
import cheerio from 'cheerio';
import hljs from 'highlight.js'
import 'highlight.js/styles/night-owl.css';

import Layout from '../../components/Layout'

type Props = {
  blog: {
    id: string,
    createdAt: string,
    updatedAt: string,
    publishedAt: string,
    revisedAt: string,
    title: string,
    tags: any[],
    body: any
  },
  highlightedBody: any
}

type Tag = {
  id: string,
  createdAt: string,
  updatedAt: string,
  publishedAt: string,
  revisedAt: string,
  name: string
}

const BlogId: React.FC<Props> = ({ blog,highlightedBody }) => {
  return (
    <Layout>
      <h1>{blog.title}</h1>
      <div>
        {blog.tags.map((tag: Tag) => (
          <React.Fragment key={tag.id}>
            <span>{tag.name}</span>
          </React.Fragment>
        ))}
      </div>
      <div dangerouslySetInnerHTML={{ __html: highlightedBody }}></div>
    </Layout>
  )
}

export const getStaticPaths = async () => {
  const key: any = {
    headers: { 'X-API-KEY': process.env.API_KEY },
  }

  const res = await fetch('https://hogehoge.microcms.io/api/v1/blogs', key)
  const repos = await res.json()

  const paths = repos.contents.map((repo: any) => `/blogs/${repo.id}`)
  return { paths, fallback: false }
}

export const getStaticProps = async (context: any) => {
  const id = context.params.id

  const key: any = {
    headers: { 'X-API-KEY': process.env.API_KEY },
  }

  const res = await fetch(
    `https://hogehoge.microcms.io/api/v1/blogs/${id}`,
    key,
  )
  const blog = await res.json()
  const $ = cheerio.load(blog.body)

  $('pre code').each((_, elm) => {
    const result = hljs.highlightAuto($(elm).text())
    $(elm).html(result.value)
    $(elm).addClass('hljs')
  })
  return {
    props: {
      blog,
      highlightedBody:$.html()
    },
  }
}

export default BlogId

```