🗒️

エンジニアなら自分でブログを作れ!③Markdownのカスタマイズ編

2022/09/29に公開
3

初めに

この記事は「エンジニアなら自分でブログを作れ!」の第三弾です。
第一弾や第二弾を読んでいなくても問題ありませんが、特に第一弾では無料の自作ブログを作成するために必要な導入について解説しているので良ければご覧ください。

第一弾↓

https://zenn.dev/miketako3/articles/9b2b1a9ec13901

第二弾↓

https://zenn.dev/miketako3/articles/bfdc1b09ba8ed3

この記事はNext.jsの
blog-starter
(公式のデモページ)
について、デフォルトではとてもリッチとは言い難いMarkdownの出力をzennと同様のものにする方法を解説します。

デフォルトのmarkdown出力

blog-starterにおけるMarkdownからHTMLの出力は lib/markdownToHtml.ts に実装されています。

export default async function markdownToHtml(markdown: string) {
  const result = await remark().use(html).process(markdown)
  return result.toString()
}

これはremarkというライブラリを経由して単純にHTMLにする実装のみが含まれています。

remarkについては以下が詳しいです。

https://vivliostyle.github.io/vivliostyle_doc/ja/vivliostyle-user-group-vol2/spring-raining/index.html

また、CSSも components/markdown-styles.module.css で定義された最小限しかありません。

.markdown {
  @apply text-lg leading-relaxed;
}

.markdown p,
.markdown ul,
.markdown ol,
.markdown blockquote {
  @apply my-6;
}

.markdown h2 {
  @apply text-3xl mt-12 mb-4 leading-snug;
}

.markdown h3 {
  @apply text-2xl mt-8 mb-4 leading-snug;
}

しかし、最近のブログサービスはもっとリッチなコンテンツを入れられてCSSも綺麗ですよね。ここは是非ともカスタマイズしたいです。

zenn-editor

この記事が投稿されているZennで使われている変換機構やCSSは、Github上でMITライセンスで公開されています。とてもありがたいことです。

https://github.com/zenn-dev/zenn-editor/tree/main

これを使っていきましょう。

組み込み

インストール

https://github.com/miketako3/blog-example/commit/eaecde48625d71520348e2c3b99f225fe1951917

まずは必要なパッケージをインストールしましょう。

過去の記事に従っていればDockerで環境構築されているはずなので、まずは立ち上げましょう。

$ docker-compose up -d

次に、以下のコマンドでコンテナ内でyarnコマンドを使ってインストールしましょう。

$ docker-compose exec app yarn add zenn-markdown-html@0.1.106 zenn-markdown-css@0.1.106 zenn-embed-elements@0.1.106

zenn-markdown-html

https://github.com/miketako3/blog-example/commit/63a40f4a5a87d3a3bf89eca5e316a2c1006c612a

その名の通りZennで使われているMarkdownをHTMLに変換するパッケージです。

使い方は非常にシンプルで、lib/markdownToHtml.ts の変換部分を置き換えるだけです。

- import { remark } from 'remark'
- import html from 'remark-html'
+ import m2h from "zenn-markdown-html";

export default async function markdownToHtml(markdown: string) {
-  const result = await remark().use(html).process(markdown)
-  return result.toString()
+  return m2h(markdown)
}

これでhtmlへの変換がリッチになり、ZennのMarkdown記法も変換できるようになったはずです。

zenn-content-css

https://github.com/miketako3/blog-example/commit/b922192af2dcf84cc3645111170769c70677c4e1

Zennで使われているCSSです。こちらも組み込んでいきましょう。

まず pages/_app.tsx でCSSをインポートします。

import { AppProps } from 'next/app'
import '../styles/index.css'
+ import 'zenn-content-css'

export default function MyApp({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />
}

このCSSはzncというクラスがついている要素の配下で機能します。ですので components/post-body.tsx を以下のように変更しましょう。

const PostBody = ({content}: Props) => {
  return (
-       <div className="max-w-2xl mx-auto">
+       <div className="max-w-2xl mx-auto znc">
        <div
            className={markdownStyles['markdown']}
            dangerouslySetInnerHTML={{__html: content}}
        />
      </div>
  )
} 

これでCSSの組み込みも完了です!

zenn-embed-elements

https://github.com/miketako3/blog-example/commit/dd6478e2f71a96c81421821400754fe8e8ca5997

次にZennの埋め込みコンテンツへの対応もやっていきます。

pages/posts/[slug].tsx を編集します。

import { useRouter } from 'next/router'
import ErrorPage from 'next/error'
import Container from '../../components/container'
import PostBody from '../../components/post-body'
import Header from '../../components/header'
import PostHeader from '../../components/post-header'
import Layout from '../../components/layout'
import { getPostBySlug, getAllPosts } from '../../lib/api'
import PostTitle from '../../components/post-title'
import Head from 'next/head'
import { CMS_NAME } from '../../lib/constants'
import markdownToHtml from '../../lib/markdownToHtml'
import type PostType from '../../interfaces/post'
+ import {useEffect} from "react";
+ import initTwitterScriptInner from "zenn-embed-elements/lib/init-twitter-script-inner";

type Props = {
  post: PostType
  morePosts: PostType[]
  preview?: boolean
}

export default function Post({ post, morePosts, preview }: Props) {
  const router = useRouter()
  if (!router.isFallback && !post?.slug) {
    return <ErrorPage statusCode={404} />
  }
+   useEffect(() => {
+     import('zenn-embed-elements');
+   }, []);
  return (
    <Layout preview={preview}>
+       <script
+           dangerouslySetInnerHTML={{
+             __html: initTwitterScriptInner
+           }}
+       />
      <Container>
        <Header />
        {router.isFallback ? (
          <PostTitle>Loading…</PostTitle>
        ) : (
          <>
            <article className="mb-32">
              <Head>
                <title>
                  {post.title} | Next.js Blog Example with {CMS_NAME}
                </title>
                <meta property="og:image" content={post.ogImage.url} />
              </Head>
              <PostHeader
                title={post.title}
                coverImage={post.coverImage}
                date={post.date}
                author={post.author}
              />
              <PostBody content={post.content} />
            </article>
          </>
        )}
      </Container>
    </Layout>
  )
}

これで埋め込みコンテンツに対応できました!

確認

ブログの投稿をいじって色々試してみましょう。_posts/hello-world.md の本文にZenn公式が出しているMarkdownの使い方の記事をそのまま入れてみます。

https://zenn.dev/zenn/articles/markdown-guide

長いので差分はここには貼りません。Github上で確認してください。

https://github.com/miketako3/blog-example/commit/454c9f4bc7060a644547752458f5d200aa315134

(zenn-docsのライセンスは書かれていませんでしたが引用の範囲で利用しているつもりです。まずかったら教えてください。)

最新版では無いからかごく一部使えないものもありますが、問題ない範囲だと思います。

以下のスクリーンショットはZennのものではなく、ローカルで立ち上げたブログのものです。

まとめ

この記事では自作ブログにZennのMarkdownとCSSを導入するやり方を解説しました。

次回もブログをカスタマイズしていく方法について解説したいと思っていますので、是非よろしくお願いします。

最後に、私の投稿はZenn以外の投稿もまとめて以下ブログで取得できますので、良ければ見てみてください。

https://blog.miketako.xyz

追記

次回

https://zenn.dev/miketako3/articles/2afb29824e578d

Discussion

となりの。となりの。

素敵な投稿をありがとうございます。
必要なパッケージをインストールするコマンドのCSSのパッケージ名が
zenn-markdown-css となっていますが、zenn-content-css
ではないかと思いましたので、コメントさせていただきます。

みけたこみけたこ

おそらくzennのライブラリに変更があり、手順が一部陳腐化しているようです。
詳しくはzenn-editorのリポジトリを参照してください。