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

8 min read読了の目安(約7900字

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-typescript でひな形を作成
  2. zenn-markdown-htmlzenn-content-csszenn-embed-elements を導入する
  3. ローカルで適用されたことを確認する

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

バージョン情報

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

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

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

yarn create next-app --example blog-starter-typescript .
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'; する

zenn-embed-elements

  • ブラウザ側で埋め込みコンテンツを生成します。Appコンポーネントで useEffect(() => import('zenn-embed-elements'), []) と書く
  • 同じく Appコンポーネントで import initTwitterScriptInner from 'zenn-embed-elements/lib/init-twitter-script-inner';
  • その後 <script> タグに initTwitterScriptInner を設定

ブログサンプルの変更点

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

その後、yarn install

pages/posts/[slug].tsx
- import markdownToHtml from '../../lib/markdownToHtml'
+ import 'zenn-content-css';
+ 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" >
                            <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 '../styles/index.css'
import { useEffect } from 'react';
+import initTwitterScriptInner from 'zenn-embed-elements/lib/init-twitter-script-inner';

export default function MyApp({Component, pageProps}: AppProps) {
+    useEffect(() => import('zenn-embed-elements'), [])
-    return <Component {...pageProps} />
+    return (<>
+        <script
+            dangerouslySetInnerHTML={{
+                __html: initTwitterScriptInner
+            }}
+        />
+        <Component {...pageProps} /></>
+    )
}

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にプルリクを送ると歓迎されると思います!