Munus

background

Astro製のブログで読んだ本リストを管理するためにmicroCMSを使ってみた

目次

はじめに#

Web クリエイターボックスみたいに読んだ本リストを自分のプラットフォームで管理したいと思い、このブログ内に読んだ本リストを追加しました。サイドバーにリンクがあるので覗いてみてください!

読んだ本の管理に microCMS を使ったので、Astro 製のブログで本の管理を microCMS で行う方法をまとめます。まだ、データ数が少ないのでページネーションは実装してないのですが、実装したら追記します。

microCMS の準備#

microCMS のアカウントを作成して、コンテンツ(API)を作成し API 設定をしましょう。下記のように設定します。

APIスキーマ
APIスキーマ

読んだ本リスト API のエンドポイントは book にしました。

API スキーマの例#

本のリストなので、自分は下記のように設定しました。

フィールド ID表示名種類
titleタイトルテキストフィールド
reviewレビューリッチエディタ
urlAmazon リンクテキストフィールド
cover表紙画像テキストフィールド
author著者テキストフィールド
publishedDate発売日日時
categoryカテゴリーコンテンツ参照
postlink紹介記事テキストフィールド

環境変数の設定#

microCMS の API にアクセスするために、登録情報と API キーを環境変数に設定しましょう。
クライアントサイドで記事ページの下にも読んだ本を表示したかったので、PUBLICを先頭につけています。PUBLICを先頭に付けるとクライアントサイドでも環境変数を読み込めるようになります。

.env
PUBLIC_MICROCMS_SERVICE_DOMAIN=<YOUR_SERVICE>
PUBLIC_MICROCMS_API_KEY=<YOUR_API_KEY>

microCMS JavaScript SDK のインストール#

microCMS を Astro で使うために、microcms-js-sdk をインストールしましょう。

npm install microcms-js-sdk

読んだ本リストを作成する#

microCMS の API を使って、読んだ本リストを作成しましょう。今回は/bookページに読んだ本リストの一覧を表示するサイトを想定します。

まずは、microCMS にある読んだ本リストの全件とカテゴリーを取得するコードを書きます。src/scripts/library/microcms.tsに API 呼び出しのコードを書きます。

src/scripts/library/microcms.ts
// SDK利用準備
import type { MicroCMSListContent, MicroCMSQueries } from "microcms-js-sdk";
import { createClient } from "microcms-js-sdk";
 
// クライアントの作成
const client = createClient({
  serviceDomain: import.meta.env.PUBLIC_MICROCMS_SERVICE_DOMAIN,
  apiKey: import.meta.env.PUBLIC_MICROCMS_API_KEY,
});
 
// 型定義
export type Books = {
  title: string;
  review: string;
  url: string;
  cover: string;
  author: string;
  category: BooksCategory;
  publishedDate: Date;
  postlink: string;
} & MicroCMSListContent;
 
export type BooksCategory = {
  name: string;
  slug: string;
} & MicroCMSListContent;
 
// 本の一覧を全件取得
export const getBooks = async (queries?: MicroCMSQueries) => {
  return await client.getAllContents<Books>({ endpoint: "books", queries });
};
 
// カテゴリーの一覧を取得
export const getBooksCategory = async (queries?: MicroCMSQueries) => {
  return await client.getList<BooksCategory>({ endpoint: "category", queries });
};

client.getListメソッドもありますが、こちらは取得数に制限があるので、全件取得するclient.getAllContentsを使っています。引数にはエンドポイント(books や category)を設定して、クエリは関数の引数から渡すことができます。

詳しいクエリパラメータに関しては、公式ドキュメントを参照してください。

次に、src/pages/book/index.astroに読んだ本リストを表示するコードを書いていきましょう。

src/pages/book/index.astro
---
import MainGridLayout from "@/layouts/MainGridLayout.astro";
import { getBooks, getBooksCategory } from "@/scripts/library/microcms";
 
// 本リストとカテゴリーを取得
const books = await getBooks({ fields: ['id', 'title', 'cover', 'category']});
const categories = await getBooksCategory({ fields: ['id', 'name', 'slug']});
---
 
<MainGridLayout>
  <ul class="category__list card-base">
    {categories.contents.map((category) => (
      <li class="category__item">
        <a class="category__link btn-regular" href={`/book/category/${category.slug}`}>
          {category.name}
        </a>
      </li>
    ))}
  </ul>
  <ul class="book__list">
    {books.map((book) => (
      <li>
        <a class="book__card" href={`/book/${book.id}`}>
          <div class="book__card-img">
            <img src={book.cover} alt={book.title} />
          </div>
          <div class="book__card-title">
            {book.title}
          </div>
        </a>
      </li>
    ))}
  </ul>
</MainGridLayout>

ここでは、先程作成したgetBooksgetBooksCategoryを使って、読んだ本リストとカテゴリーを取得し map で回して表示しています。fields には取得したいデータのキーを指定します。

次に本の詳細ページとカテゴリー別のページを作成しましょう。

本の詳細ページを作成する#

本の詳細データを取得するコードを書きます。src/scripts/library/microcms.tsにコードを追加しましょう。

src/scripts/library/microcms.ts
export const getBooksDetail = async (
  contentId: string,
  queries?: MicroCMSQueries
) => {
  return await client.getListDetail<Books>({
    endpoint: "books",
    contentId,
    queries,
  });
};

getListDetailメソッドは contentId を指定して、指定した ID のデータを取得することができます。

続いて本の詳細ページ用にsrc/pages/book/[bookId].astroを作成しましょう。getBooksDetail関数を使って、本の詳細データを取得して表示します。

src/pages/book/[bookId].astro
---
import MainGridLayout from "@/layouts/MainGridLayout.astro";
import { getBooks, getBooksDetail } from "@/scripts/library/microcms";
import { Icon } from "astro-icon/components";
 
// 詳細記事ページの全パスを取得
export async function getStaticPaths() {
  const response = await getBooks({ fields: ['id'] });
  return response.map((content) => ({
    params: { bookId: content.id },
  }));
}
 
// 詳細記事ページのデータを取得
const { bookId } = Astro.params;
const book = await getBooksDetail(bookId as string);
// 日付をフォーマット
const date = new Date(book.publishedDate as Date);
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
---
 
<MainGridLayout>
  <div class="book__detail card-base">
    <h1 class="book__detail-title">{book.title}</h1>
    <div class="book__detail-body">
      <div class="book__detail-right">
        <a href={book.url} class="book__detail-img" target="_blank">
          <img src={book.cover} alt={book.title} />
        </a>
        <div class="book__detail-links">
          <a href={book.url} class="book__detail-link btn-regular" target="_blank">Amazon</a>
          {book.postlink && (
            <a href={`/blog/${book.postlink}`} class="book__detail-link btn-regular">記事を見る</a>
          )}
        </div>
      </div>
      <dl class="book__detail-info">
        <div class="book__detail-info-category-wrap">
          <a href=`/book/category/${book.category.slug}` class="book__detail-info-category">
            {book.category.name}
          </a>
        </div>
        <div class="book__detail-info-item">
          <dt>著者</dt>
          <dd>{book.author}</dd>
        </div>
        <div class="book__detail-info-item">
          <dt>発売日</dt>
          <dd>{`${year}${month}${day}日`}</dd>
        </div>
        <div class="book__detail-info-body" set:html={book.review}></div>
      </dl>
    </div>
    <div class="book__detail-bottom-link-wrap">
      <a href="/book" class="book__detail-bottom-link">
        <span class="link__icon">
          <Icon name="material-symbols:chevron-left-rounded" />
        </span>
        <span class="book__detail-bottom-link-text">読んだ本リストに戻る</span>
      </a>
    </div>
  </div>
</MainGridLayout>

getStaticPaths関数は動的なパスがどういう値をとるかを定義します。ここでは、getBooks関数で取得した ID をパスとして設定しています。

book.reviewはリッチエディタで入力したデータなので、改行などを正しくレンダリングするためset:htmlを使って HTML をそのまま表示します。

これで本の詳細ページができました。

カテゴリーページを作成する#

カテゴリー別のページを作成しましょう。src/pages/book/category/[slug]/index.astroを作成します。

src/pages/book/category/[slug]/index.astro
---
import MainGridLayout from "@/layouts/MainGridLayout.astro";
import { getBooks, getBooksCategory } from "@/scripts/library/microcms";
 
// カテゴリー別のパスを取得
export async function getStaticPaths() {
  const response = await getBooks({ fields: ['id', 'category'] });
 
  return response.map((content) => ({
    params: { slug: content.category.slug },
    props: {
      categoryId: content.category.id,
      slug: content.category.slug,
      name: content.category.name,
    },
  }));
}
 
const { categoryId, slug, name } = Astro.props;
 
// カテゴリー別の本のリストを取得
const books = await getBooks({
  fields: ['id', 'title', 'cover', 'category'],
  filters: `category[equals]${categoryId}`
});
 
const categories = await getBooksCategory({ fields: ['id', 'name', 'slug']});
---
 
<MainGridLayout>
  <ul class="category__list card-base">
    {categories.contents.map((category) => (
      <li class="category__item">
        <a class="category__link btn-regular" href={`/book/category/${category.slug}`}>
          {category.name}
        </a>
      </li>
    ))}
  </ul>
  <ul class="book__list">
    {books.map((book) => (
      <li>
        <a class="book__card" href={`/book/${book.id}`}>
          <div class="book__card-img">
            <img src={book.cover} alt={book.title} />
          </div>
          <div class="book__card-title">
            {book.title}
          </div>
        </a>
      </li>
    ))}
  </ul>
</MainGridLayout>

ここでもgetStaticPaths関数を使って、カテゴリー別のパスを取得しています。getBooksの引数にfiltersをでカテゴリー ID を指定することで、そのカテゴリーの本のリストを取得できます。

category[equals]${categoryId}のように[equals]を使うことで、id と一致する本のデータを取得できます。

以上で、本の一覧・詳細・カテゴリー別のページができました!
最後におまけでブログ記事の下に PR として本のリストをランダムで 4 件表示する方法を紹介します。

おまけ: ブログ記事の下に本のリストをランダムで 4 件表示する#

新しくコンポーネントとしてPostBookArea.astroを作成しました。このコンポーネントではクライアントサイドで JavaScript を使って、microCMS の API からデータを取得しています。

このブログは SSG として公開しているので、Astro のコードフェンスの中で取得すると、ビルド時にデータを取得するので固定になってしまいます。なので、クライアントサイドで取得するように<script>の中で microCMS の API からデータを取得するコードを書いていきます。

src/components/PostBookArea.astro
<div class="post-book-area card-base">
  <h2>PR</h2>
 
  <ul class="post-book-area__list"></ul>
</h2>
 
<script>
  import { getBooks } from "@/scripts/library/microcms";
 
  const allBooks = await getBooks({ fields: ['title', 'cover', 'url'] });
  const randomBooks = allBooks.sort(() => Math.random() - 0.5).slice(0, 4);
  const bookList = document.querySelector(".post-book-area__list");
 
 
  if (randomBooks && bookList) {
    bookList.innerHTML = randomBooks.map((book) => `
      <li class="post-book-area__item">
        <a class="post-book-area__link" href="${book.url}" target="_blank">
          <div class="post-book-area__img">
            <img src="${book.cover}" alt="${book.title}" />
          </div>
          <div class="post-book-area__title">${book.title}</div>
        </a>
      </li>
    `).join('');
  }
</script>

ここでは microCMS の API から取得したデータをランダムに 4 件取得して表示しています。
このコンポーネントをブログ記事の詳細ページに追加します。

src/pages/blog/[slug]/index.astro
---
import PostBookArea from "@/components/PostBookArea.astro";
---
 
<MainGridLayout>
  <div class="article card-base">
    <MarkdownBody>
      <Content components={{ img: Figure }} />
    </MarkdownBody>
 
  <div class="post-book-area">
    <PostBookArea />
  </div>
 
</MainGridLayout>

これでブログ記事の下に本のリストをランダムで 4 件表示することができました。

まとめ#

Astro 製のブログに microCMS で管理した読んだ本リストを表示する方法を紹介しました。読書メーターも使ってましたが、自分のプラットフォームで読んだ本を管理することができるようになったので、気軽に読んだ感想を残せるようになってよかったです!

サイドバーにリンクがあるのと、このページにあるので覗いてみてください!

参考#

PR