Next.jsとmicroCMSで技術ブログを作る(2)

公開 2022年01月30日更新 2022年02月03日

Next.jsReact.jsTypeScriptmarkdownJamstack

この記事は前回の続きです。

記事の目的と注意点

Next.jsとmicroCMSでJamstackブログを構築する方法を解説します。
手順を示すことが目的なので、コードやCSSの詳細な解説は行いません。
またCSSによるスタイリングも最低限となっています。

この記事でやること

  1. シンタックスハイライトの導入
  2. フォーマットを指定して日付を表示
  3. 記事一覧画面の実装

1. シンタックスハイライトの導入

シンタックスハイライトを適用するためのライブラリは色々あるのですが、今回はhighlight.jsを使いました。

まずはhighlight.jsをインストールします。

npm i highlight.js

 
またNode.jsでDOM操作する必要があるので、jsdomをインストールします。

npm i jsdom
npm i -D @types/jsdom

 
pages/[id].tsxgetStaticProps()に以下を追記します。(関係ない部分は省略)

import hljs from "highlight.js";
import "highlight.js/styles/github-dark.css";
import { JSDOM } from "jsdom";

// ...

export const getStaticProps: GetStaticProps<Props, Params> = async ({
    params,
}) => {
    // ...

    // シンタックスハイライトを追加
    const document = new JSDOM(String(content)).window.document;
    document.querySelectorAll<HTMLElement>("pre code").forEach((el) => {
        hljs.highlightElement(el);
    });
    articleData.content = document.body.innerHTML;
    // 以下の1行は削除
    // articleData.content = String(content);

    // ...
}

 
これだけでシンタックスハイライトを導入できます。
以下を実行して動作確認してみます。

npm run dev

 
以下のようになります。
add-syntax-highlight

シンタックスハイライトを適用するだけで見た目がかなり良くなりますね。

2. フォーマットを指定して日付を表示

次は日付を2022年1月30日のように表示させます。
日付を扱うライブラリも色々あるのですが、今回はDay.jsを採用しました。

インストールします。

npm i dayjs

 
lib/date.tsを作成し、以下のように実装します。

import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import timezone from "dayjs/plugin/timezone";

dayjs.extend(utc);
dayjs.extend(timezone);

export const toFormatDate = (date: string) => {
    return dayjs.utc(date).tz("Asia/Tokyo").format("YYYY年MM月DD日");
};

Day.jsの詳しい使い方については公式リポジトリを参照してください。

上記をlib/client.tsでimportします。

import { toFormatDate } from "lib/date";

 
lib/client.tscreateArticle()を以下のようにします。

const createArticle = (response: any) => {
    return {
        id: response.id ?? "",
        pathId: response.pathId ?? "",
        title: response.title ?? "",
        content: response.content ?? "",
        publishedAt: toFormatDate(response.publishedAt ?? ""),
        revisedAt: toFormatDate(response.revisedAt ?? ""),
    } as Article;
};

もしレスポンスにpublishedAtフィールドが含まれていない場合、toFormatDate()には空文字列が渡されるためpublishedAtは"Invalid Date"という文字列になります。

動作確認してみます。

npm run dev

 
以下のようにちゃんとフォーマットされました。
add-format-date

3. 記事一覧画面の実装

次に記事一覧画面を実装します。
まず記事一覧画面に表示するコンテンツをAPIから取得する必要があるので、lib/client.tsgetArticleInfos()を実装します。

export const getArticleInfos = async () => {
    const res = await client.getList({
        endpoint: "posts", // APIのendpoint
        queries: {
            limit: 10000,
            fields: ["id", "title", "publishedAt", "revisedAt"],
            orders: "-publishedAt", // 新しい記事が前にくるようにする
        },
    });
    return toArray(res.contents);
};

toArray()前回の記事で実装した関数で、レスポンスをArticle型の配列にしてreturnします。

次に記事1つを表すCard.tsxというコンポーネントを実装します。
プロジェクトルートにcomponentsディレクトリを作成し、そこにCard.tsxを実装します。

import React from "react";
import Link from "next/link";
import styles from "./Card.module.css";

interface Props {
    title: string;
    link: string;
    publishedAt: string;
    revisedAt: string;
}

export const Card: React.FC<Props> = ({
    title,
    link,
    publishedAt,
    revisedAt,
}) => {
    return (
        <div className={styles.root}>
            <Link href={link}>
                <a className={styles.cardLink}>
                    <p className={styles.title}>{title}</p>
                    <div className={styles.dateContainer}>
                        <p>公開 {publishedAt}</p>
                        <p className={styles.revisedAt}>更新 {revisedAt}</p>
                    </div>
                </a>
            </Link>
        </div>
    );
};

Cardコンポーネントは全体が記事へのリンクになるようにします。
Next.jsではリンクをnext/linkのLinkで表し、子に必ずaタグを持つ必要があります。
このLinkについては詳しく理解していないのですが、スタイルをあてる場合はLinkの親コンポーネントか、子のaタグにあてるみたいです。

スタイルをcomponents/Card.module.cssとして作成します。

.root {
    max-width: 100%;
    height: 16vh;
    max-height: 16vh;
    padding: 5px 10px;
    box-sizing: border-box;
    margin-left: 20px;
    margin-bottom: 10px;
}

.root:hover {
    cursor: pointer;
}

.cardLink {
    color: inherit;
    text-decoration: none;
}

.cardLink:hover {
    text-decoration: none;
}

.title {
    margin-top: 20px;
    margin-bottom: 0px;
    max-height: 6.4rem;
    font-size: 2rem;
    font-weight: bold;
    line-height: 3.2rem;
    overflow-wrap: break-word;
    overflow: hidden;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
}

.dateContainer {
    display: flex;
    flex-direction: row;
    color: rgba(0, 0, 0, 0.5);
    font-size: 1.4rem;
}

.revisedAt {
    margin-left: 20px;
}

最後に記事一覧画面を実装します。
記事一覧画面はルートパス/でアクセスできるようにします。
このルートパスのページはpages/index.tsxに該当します。

pages/index.tsxを以下のようにします。

import React from "react";
import type { NextPage } from "next";
import { GetStaticProps } from "next";
import { ParsedUrlQuery } from "node:querystring";
import { getArticleInfos } from "lib/client";
import type { Article } from "types/api";
import { Card } from "components/Card";
import styles from "styles/index.module.css";

interface Props {
    articles: Article[];
}

const Home: NextPage<Props> = ({ articles }) => {
    return (
        <main>
            <section className={styles.articles}>
                {articles.map((v, i) => (
                    <Card
                        key={i + 1}
                        link={`/${v.id}`}
                        title={v.title}
                        publishedAt={v.publishedAt}
                        revisedAt={v.revisedAt}
                    />
                ))}
            </section>
        </main>
    );
};

export const getStaticProps: GetStaticProps<
    Props,
    ParsedUrlQuery
> = async () => {
    // 記事一覧を取得
    const articles = await getArticleInfos();
    return {
        props: {
            articles,
        },
    };
};

export default Home;

 
styles/index.module.cssは以下のようにします。

.articles {
    width: 100vw;
    height: auto;
}

 
動作確認をしてみます。

npm run dev

 
http://localhost:3000/にアクセスすると、以下のように記事一覧が表示されました。
index-page

まとめ

以上がブログ構築についての大まかな手順となります。
今後はこれに機能を追加していく記事を書いていこうかと思っています。

最後までお読みいただきありがとうございました!

参考リンク

microCMSブログ
microCMS + Next.jsでJamstackブログを作ってみよう

GitHub(使用したパッケージ)
highlight.js
jsdom
Day.js


前の記事
Next.jsとmicroCMSで技術ブログを作成する(1)