Taka blog

プログラミングとか

Next.jsでコードブロックにシンタックスハイライトで色を付ける

編集対象ファイルの全コード

シンタックスハイライトを付ける上で変更するのは、root/pages/blog/[id].tsxファイルのみです。最初に、このファイルの例を貼っています。

import { client } from "../../libs/client";
import type { Article } from '../../types/article';
import styles from '../../styles/Home.module.css'
import Head from 'next/head'
import cheerio from 'cheerio';
import hljs from 'highlight.js';
import 'highlight.js/styles/night-owl.css';
import {endpoint} from '../index'

type props = {
  article: Article,
  highlightedBody:string,
  tableOfContents:content[]
};

type content = {
  text: string;
  id: string;
  name: string;
}

export default function BlogId({article, highlightedBody, tableOfContents}:props) {
  return (
    <div className={styles.container}>

      <Head>
        <title>{article.title}</title>
        <meta name="description" content={article.description} />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.postmain}>
        <h1 className={styles.title}>{article.title}</h1>
        <div className={styles.toc}>
          <h4>目次</h4>
          <ul id="lists">
            {tableOfContents.map(content => {
              if (content.name == "h2"){
                return <li id={content.name} key={content.id}>
                  <a href={"#" + content.id}>{content.text}</a>
                </li>
              }else if (content.name=="h3"){
                return <ul><li id={content.name} key={content.id}>
                  <a href={"#" + content.id}>{content.text}</a>
                </li></ul>
              }else if (content.name=="h4"){
                return <ul><ul><li id={content.name} key={content.id}>
                  <a href={"#" + content.id}>{content.text}</a>
                </li></ul></ul>
              }
            })}
          </ul>
        </div>
        <div className={styles.post} dangerouslySetInnerHTML={{__html: highlightedBody}}/>
      </main>

    </div>
  );
}

// パス指定
export const getStaticPaths = async () => {
  const data = await client.get({ endpoint: endpoint });
  const articles: Article[] =  data.contents;
  const paths = articles.map((content) => `/`+endpoint+`/${content.id}`);
  return { paths, fallback: false };
};

// データをテンプレートに受け渡す
export const getStaticProps = async (context: any) => {
  const id = context.params.id;
  const res:Article = await client.get({ endpoint: endpoint, contentId: id });

  // autoでシンタックスハイライトを付ける
  const $ = cheerio.load(res.body)
  $('pre code').each((_, elm) => {
    const result = hljs.highlightAuto($(elm).text())
    $(elm).html(result.value)
    $(elm).addClass('hljs')
  })

  const headings = $('h2, h3, h4').toArray()
  const toc:content[] = headings.map((data) => {
    return {
      //@ts-ignore
      text: String(data.children[0].data),
      id: data.attribs.id,
      name: data.name,
    }
  })

  return {
    props: {
      article:res,
      highlightedBody:$.html(),
      tableOfContents:toc
    },
  }
};

各処理の紹介

ハイライト付HTMLの作成

getStaticProps内で、ハイライトされたHTMLを作成します。

  const $ = cheerio.load(res.body)
  $('pre code').each((_, elm) => {
    const result = hljs.highlightAuto($(elm).text())
    $(elm).html(result.value)
    $(elm).addClass('hljs')
  })

  return {
    props: {
      article:res,
      highlightedBody:$.html(),
      tableOfContents:toc
    },
  }

今回は言語を指定せず、オートで言語を判断させています。
$.html()がHTML要素になるので、これを引数として渡せばOKです。

ハイライト付HTMLの表示

export default function XXX の中に記載します。

export default function BlogId({article, highlightedBody, tableOfContents}:props) {
  return (
    <div className={styles.container}>

// 省略

<div className={styles.post} dangerouslySetInnerHTML={{__html: highlightedBody}}/>

まず、引数でハイライト付HTML(例のhighlightedBody)を受け取ります。
これを表示するのに変更が必要な箇所は、例の最後の行のように、ハイライト済みのHTML要素を指定するところだけです。