この記事は前回の続きです。
記事の目的と注意点
Next.jsとmicroCMSでJamstackブログを構築する方法を解説します。
手順を示すことが目的なので、コードやCSSの詳細な解説は行いません。
またCSSによるスタイリングも最低限となっています。
この記事でやること
- シンタックスハイライトの導入
- フォーマットを指定して日付を表示
- 記事一覧画面の実装
1. シンタックスハイライトの導入
シンタックスハイライトを適用するためのライブラリは色々あるのですが、今回はhighlight.jsを使いました。
まずはhighlight.js
をインストールします。
npm i highlight.js
またNode.jsでDOM操作する必要があるので、jsdomをインストールします。
npm i jsdom
npm i -D @types/jsdom
pages/[id].tsx
のgetStaticProps()
に以下を追記します。(関係ない部分は省略)
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
以下のようになります。
シンタックスハイライトを適用するだけで見た目がかなり良くなりますね。
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.ts
のcreateArticle()
を以下のようにします。
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
以下のようにちゃんとフォーマットされました。
3. 記事一覧画面の実装
次に記事一覧画面を実装します。
まず記事一覧画面に表示するコンテンツをAPIから取得する必要があるので、lib/client.ts
にgetArticleInfos()
を実装します。
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/
にアクセスすると、以下のように記事一覧が表示されました。
まとめ
以上がブログ構築についての大まかな手順となります。
今後はこれに機能を追加していく記事を書いていこうかと思っています。
最後までお読みいただきありがとうございました!
参考リンク
microCMSブログ
microCMS + Next.jsでJamstackブログを作ってみよう
GitHub(使用したパッケージ)
highlight.js
jsdom
Day.js