created: 2020-11-08 02:36:51,tags:javascript,next,nodejs,netlify,mdx,ssg,tailwindcss,

Netlify, Next.js, MDX で SSG ブログを作った

ずっと Next.js (というか、React.js) を覚えようと思ってはいたものの、大したやる気がでず放置してたけど、ようやく重い腰を上げて使い始めてみた。
仕事の合間でちょこちょこと進めていただけなので、3 週間くらい掛かったけどようやく見れるレベルのものになったため公開する。

構成としては、Netlify + Next.js + MDX で静的サイトジェネレートする流れのため n 番煎じレベルのものではあるけど、個人的には初めての試みだったため、それなりに調べて理解するのに時間が掛かった。
やった感想としては MDX の導入周りでどこも不親切な感じで、結局ソースと Next.js のドキュメントを見ながら考える必要があったので、実はまだまだ普及してないセットなのか?とか思ってる (みんなの評価みててもそんなにやってる数を見ないし、SSG や Dynamic Routes あたりが最近入ったものっぽいし...)。

あと、front matter の名前を知らなかったため、MDX のメタ部分を調査する時に微妙に苦戦した。もう忘れない。

MDX は下記のように設定し、title, description, created_at, tags を記事に埋め込んでおき、SSG の際にファイル内から情報を取得、収集して JSON で保持という形に落ちついた。
JSON のサイズ大きくなったらどうしようとかは現状なにも考えてないし、ページネーションも考慮していないけど、その点については後で考える事にした。

下記は mdx でブログを記載する際のサンプル。

next-ssg-blog.mdx
---
layout: blog-page
title: sampleタイトル
description: test
created_at: 2020-11-07 00:00:00
tags: [next, nodejs, netlify, mdx, ssg]
---

# sampleタイトル

ここに記事を書く

また、tag の一覧が作成したかったので、Dynamic Routes で tag を処理するページを作成した。
あまり正解がイメージできてないまま書いたので結構適当だけど、今のところおかしさはないのでコレで使っていく。

[tag].jsx
import Head from 'next/head';
import Blog from '@/components/Blog';
import Link from 'next/link';
import dynamic from 'next/dynamic';
import { useRouter } from 'next/router';

export default function Home({ posts, query }) {
    const router = useRouter();
    const { tag } = router.query;

    return (
        <Blog title={`${tag} | Blog`}>
          <Head>
            <meta name="description" content={`tag ${tag} | Blog`} />
          </Head>

          <div className="container relative rounded-lg block lg:flex-none items-center bg-white shadow-xl my-20 mx-3 lg:max-w-screen-lg sm:mx-2 md:mx-auto">
            {
                posts.map((post) => (
                    <div key={ post.path } className="mx-10 divide-solid divide-y divide-gray-500">
                      <div className="w-auto h-20 h-min-full px-5 py-3 box-content">
                        <p className="text-gray-600">
                          <span className="mr-3">created: { post.created_at },</span>

                          tags:
                          {
                              post.tags.map((tag) => (
                                  <Link href={`/blog/tag/${tag}`} key={ post.path + '-' + tag}>
                                    <a className="hover:underline mr-1 pl-1 py-1">{ tag },</a>
                                  </Link>
                              ))
                          }
                        </p>
                        <Link href={ post.path }>
                          <a className="hover:underline">
                            <h3 className="text-lg pl-5 pt-3 pb-5">{ post.title }</h3>
                          </a>
                        </Link>
                      </div>
                    </div>
                ))
            }
          </div>
        </Blog>
    );
}

export async function getStaticPaths() {
    const fs = require('fs');
    const fm = require('front-matter');
    const glob = require('glob');
    const dateFormat = require('dateformat');

    let tags = new Set();
    glob.sync('./**/*.mdx').map((path) => {
        const file = fs.readFileSync(path, 'utf8');
        const content = fm(file).attributes;

        content.tags.map((tag) => {
            tags.add(tag);
        });
    });

    const paths = [...tags].map((tag) => {
        return {
            params: {
                tag
            }
        };
    });

    return { paths, fallback: false };
}

export async function getStaticProps({ params }) {
    process.env.TZ = 'UTC';
    const fs = require('fs');
    const fm = require('front-matter');
    const glob = require('glob');
    const dateFormat = require('dateformat');

    let posts = glob.sync('./**/*.mdx').map((path) => {
        const file = fs.readFileSync(path, 'utf8');
        const content = fm(file).attributes;
        return {
            path: path.replace('./pages', '').replace('.mdx', ''),
            title: content.title,
            tags: content.tags,
            created_at: dateFormat(content.created_at, "yyyy-mm-dd HH:MM")
        };
    }).filter((post) => {
        return post.tags.includes(params.tag);
    });

    posts.sort((a, b) => {
        return a.created_at < b.created_at ? 1 : -1;
    });

    return {
        props: {
            posts,
        }
    };
}

Next.js で作ってみた感想としては、Nuxt.js とは React.js と Vue.js の違いが大きいせいで、まだフレームワークレベルでの感想に行きつかないなぁという感じ。

tailwindcss も今回初使用だったので、実は構築のかなりの部分は tailwindcss の調整とかだったりもした。
これに関しては、一度慣れると非常に楽に構築できるので、今回触ってみて本当に良かった。

とりあえず、これでブログが書けるレベルの体制はできたのでよしとする。