Zenn Tech Blog
👩‍💻

Next.js製 個人ブログに zenn-markdown-html と zenn-content-css を導入する

2021/05/28に公開
2

Next.js でビルド時に静的コンテンツを生成し、成果物を Amazon S3 などでホスティングすれば Webページとして公開できます。個人のブログを公開するハードルが下がりました。よくあるパターンとしては、Markdown ファイルを記事のソースにし、Static Site Generator で 静的サイトを生成する手法です。blog-starter-typescript などの雛形も用意されており、始めやすいですよね。ただ、デフォルトの状態から発展させていくときに、手詰まりになりがちです。私も試みたことがあるのですが、 素の Markdown だと表現力に欠けるけどカスタマイズするハードルが高いなあ だったり、見た目を整えたいけどCSSとじっくり向き合う時間がない という理由でブロク構築が止まってしまうことがありました。

ZennのMarkdown記法一覧 でも記載されているように、Zennでパースできる記法はバリエーションが豊富です。自分でmarkdown-itなどをカスタマイズして対応させようと思うと結構大変なので、公開されている Zenn のパーザーを使ってみましょう。この記事では、公式の Next.js ブログサンプルをもとに、Zenn でも利用している zenn-markdown-htmlzenn-content-css 適用する例を示します。 MarkdownパーザーとブログサイトのCSSで困っている方はご参照ください。

適用する流れ

  1. blog-starter でひな形を作成
  2. zenn-markdown-htmlzenn-content-csszenn-embed-elements を導入する
  3. ローカルで適用されたことを確認する

今回はローカルで試します。

バージョン情報

利用ツール バージョン
Next.js 13.0.0 12.2.6
zenn-markdown-html 0.1.81
zenn-content-css 0.1.81
zenn-embed-elements 0.1.81

1.blog-starter でひな形を作成

作業ディレクトリで素直に導入します。

yarn create next-app --example blog-starter .
yarn dev

こんな感じの画面が見えればOKです。

少し構成を覗いてみましょう。Markdown を HTML に変換している箇所が導入ポイントになるはずです。

pages/posts/[slug].tsx
export async function getStaticProps({params}: Params) {
    const post = getPostBySlug(params.slug, [
        'title',
        'date',
        'slug',
        'author',
        'content',
        'ogImage',
        'coverImage',
    ])
    const content = await markdownToHtml(post.content || '')

    return {
        props: {
            post: {
                ...post,
                content,
            },
        },
    }
}

ここで Markdown を HTML に変換しているようです。まだ zenn 系のツールは入れていないので、Zenn の独自記法はうまく表示されないはずです。独自記法のページのソースを拝借して、Markdwon 文書として_postsディレクトリに配置してみましょう。

_posts/zenn-markdown.md
# Zenn 独自の記法

### メッセージ

```
:::message
メッセージをここに
:::
```

:::message
メッセージをここに
:::

```
:::message alert
警告メッセージをここに
:::
```

:::message alert
警告メッセージをここに
:::

当たり前ですがブログのサンプルに同じMarkdownを入れてもまだZennのように表示はされないですね。これを表示できるように改修します。

2.zenn-markdown-htmlzenn-content-csszenn-embed-elements を導入する

それぞれの Readme に導入方法が記載されていますが、ざっくり説明すると以下のような感じです。

zenn-markdown-html

  • Markdown コンテンツを HTML に変換する箇所で zenn 側の markdownToHtml 関数を使う

zenn-content-css

  • CSSを適用したいコンポーネントやブロックに className=znc を指定する
  • className=znc を記載した tsx ないし jsximport 'zenn-content-css'; する
    • アプリ全域で使うのであればAppコンポーネントでimportしてもOK

zenn-embed-elements

  • ブラウザ側で埋め込みコンテンツ(数式)を変換します。Appコンポーネントで useEffect(() => import('zenn-embed-elements'), []) と書く

ブログサンプルの変更点

package.json
  "dependencies": {
      ...
+    "zenn-content-css": "^0.1.131",
+    "zenn-markdown-html": "^0.1.131",
+    "zenn-embed-elements": "^0.1.131"
  }

その後、yarn install

pages/posts/[slug].tsx
- import markdownToHtml from '../../lib/markdownToHtml'
+ import markdownToHtml from 'zenn-markdown-html';

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

const Post = ({post, morePosts, preview}: Props) => {
    const router = useRouter()
    if (!router.isFallback && !post?.slug) {
        return <ErrorPage statusCode={404}/>
    }
    return (
        <Layout preview={preview}>
            <Container>
                <Header/>
                {router.isFallback ? (
                    <PostTitle>Loading…</PostTitle>
                ) : (
                    <>
-                        <article className="mb-32" >
+                        <article className="mb-32 znc" > // CSSが反応できるようにクラスを追加します
                            <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>
    )
}
_app.tsx
import { AppProps } from 'next/app'
import { useEffect } from 'react';
import '../styles/index.css'
+ import 'zenn-content-css';

export default function MyApp({Component, pageProps}: AppProps) {
+   useEffect(() => {
+     import("zenn-embed-elements"); // 数式をブラウザでレンダリングできるようにします
+   }, []);
    return <Component {...pageProps} />
}
_document_.tsx
import { Html, Head, Main, NextScript } from "next/document";

export default function Document() {
  return (
    <Html lang="en">
-     <Head />
+     <Head>
+       <script src="https://embed.zenn.studio/js/listen-embed-event.js"></script> // 一部の埋め込みで、高さ調節のためにスクリプトを利用します
+     </Head>
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}

3.ローカルで適用されたことを確認する

これでOK…?起動して確認してみましょう。

yarn dev
open http://localhost:3000/posts/zenn-markdown

Zenn の独自記法のところ、パースされてスタイルもあたってますね。

埋め込みコンテンツを試す

他の記法も確認しましょう。

Youtube

https://www.youtube.com/watch?v=WRVsOCh907o

Twitter

https://twitter.com/jack/status/20

数式(KaTeX)

$$
e^{i\theta} = \cos\theta + i\sin\theta
$$

GitHub Gist

すべて意図どおりに埋め込まれています。インストールして少しコードを変更しただけで導入できるのでありがたいですね。

おわりに

Zenn の公開されているツールをサンプルブログに適用してみました。タイトル周りや一覧画面はさらに改修が必要かと思いますが、本文には意図どおり反映できました。Next.js を使って個人ブログを構築したいけど、パーザーや記事のCSSで詰まっている…という方は、導入を検討してみてください。そして、「こういうのもパースしたいな〜」という気持ちが出てきたらzenn-dev/zenn-editorにプルリクを送ると歓迎されると思います!

Zenn Tech Blog
Zenn Tech Blog

Discussion

dashi296dashi296

個人ブログに導入させていただきました!
全体的なスタイリングはもちろん、リンクをカードにしてくれたり、codesandboxなどのサービスを簡単に埋め込めたりとてもありがたいです!

こちらの記事を参考に導入するときに、こちらのissueの問題に当たったので共有いたします。
https://github.com/zenn-dev/zenn-editor/issues/293

こちらの記事と同じことをする場合は
zenn-markdown-html, zenn-content-css, zenn-embed-elementsのバージョンをv0.1.106に設定する必要があります。

現在は最新バージョンで利用できるようです

waddy_uwaddy_u

うおおおコメントを見逃しており申し訳ないです...!こちらでもご指摘頂いていたのですね!zenn-community への報告、ありがとうございました!ご存知のとおり、現在は最新バージョンが利用できるように整備済みです。