はきくま!制作日記:microCMS 編②

投稿日

microCMS の API をイラストと日記に広げたときの記録。


その1:イラスト用の API(illusts)

無料プランは API が5つまで。作品・各話・カップリングの3つに続く4つ目で、残りは1つ(日記用に確保)。

API 主なフィールド
illusts(イラスト) image(画像), caption(テキスト・任意), tags(セレクト・複数・任意), novel(参照: novels・任意)
  • 1コンテンツに画像1枚。グリッド表示にするので、複数枚の投稿にはしない。漫画は投稿しない前提
  • title は持たせない。フィールドIDは最初 illust だったが image に改名した。型と表示側の名前がずれると、illust.image.url が読めずにエラーになる
  • R18 のフィールドは作らない(イラストに R18 は出さない想定)
  • 作品との紐づけは参照(任意)。作品に紐づかないものは、絞り込みで「その他」になる
// src/lib/types.ts
export type Illust = {
  id: string;
  image: { url: string; width: number; height: number };
  caption?: string;
  tags?: string[];
  novel?: Pick<Novel, "id" | "title" | "sort">;
  publishedAt: string;
  updatedAt: string;
};

取得は、getAllContents で全件を取り、Promise をモジュールに保持して、ビルド中の呼び出しを1回にまとめた。

// src/lib/getIllusts.ts
let cache: Promise<Illust[]> | null = null;

export function getIllusts() {
  cache ??= client.getAllContents<Illust>({
    endpoint: "illusts",
    queries: { orders: "-publishedAt" },
  });
  return cache;
}
// ほかに getIllustTags(タグの一覧)、getIllustNovels(sort 順の作品一覧)、ILLUSTS_PER_PAGE = 24

並びは publishedAt の降順。updatedAt にすると、キャプションを直しただけで先頭に来てしまう。


その2:絞り込みは JS でやる

「作品」と「タグ」で絞り込み、ページ送りもしたい。静的に作ると、作品×タグの組み合わせごとにページが必要になる。タグは複数選べるので現実的ではなく、JS で絞り込むことにした。

  • 全件を li として出力し、data-tags(JSON)と data-novel を持たせる。25件目以降は hidden
  • 状態は { novel, tags, page }。作品は1つ、タグは複数選べて OR 検索(AND にすると絞れすぎる)
  • 作品を選び直したら、タグは全解除に戻す
  • 状態は ?novel=…&tag=…&tag=…&page=N として history.replaceState で URL に反映する。直リンクや更新でも状態が戻る
  • 作品に紐づかないイラストが1枚でもあれば、「その他」を出す(data-value="other"、data-novel が空のもの)

その3:novels に thumbnail

novels に画像フィールド thumbnail(任意)を追加した。ホームの小説カード用。

thumbnail?: { url: string; width: number; height: number };
  • 任意にしてあるので、表示側は有無を確認してから使う
  • microCMS で画像を入れて公開し直さないと、API には出てこない。出ない時は npm run dev を再起動する

気になる点(未対応):画像の配信元

ギャラリーのサムネイルとホームの画像は、https://images.microcms-assets.io/…?w=480&fm=webp のように、microCMS の URL をそのまま <img> に書いている。この形だと、HTML は静的に生成していても、画像だけは閲覧のたびに microCMS から配信され、無料プランの月20GBの転送量に数える。

イラストが増えるとここが効いてくるので、増えてきたら astro:assets の getImage でビルド時に取り込む形に寄せるか検討する。まだ何も変えていない。


その4:日記用の API(diaries)

5つ目の API。無料プランの枠はこれで使い切った(novels / chapters / couplings / illusts / diaries)。

API 主なフィールド
diaries(日記) title, body(テキストエリア), tags(セレクト・複数・任意), byAi(真偽値)
  • エンドポイントは他に合わせて複数形の diaries。管理画面のエンドポイント欄と1文字でも違うと 404 になる。表示名ではなく、英数字のエンドポイント欄の値を使う
  • 日付専用のフィールドは作らない。publishedAt(投稿日)と updatedAt(更新日)が自動で付く
  • 本文は、小説(テキストエリア+独自記法)や設定資料(リッチエディタ)とは別に、日記はマークダウンで持つ
// src/lib/types.ts
export type Diary = {
  id: string;
  title: string;
  body: string;
  tags?: string[];
  byAi: boolean;
  publishedAt: string;
  updatedAt: string;
};

取得は getIllusts と同じ形で、Promise を保持して1回にまとめた。

タグ未設定の日記は「未分類」にする

タグを管理画面で選び忘れても、必ず何かのタグが付くようにしたい。管理画面側に「未選択ならこの値」というデフォルト値の設定はないので、取得の一か所で補う。

// src/lib/getDiaries.ts
export const UNCATEGORIZED = "未分類";

export function getDiaries() {
  cache ??= client
    .getAllContents<Diary>({ endpoint: "diaries", queries: { orders: "-publishedAt" } })
    .then((list) =>
      list.map((d) => ({ ...d, tags: d.tags?.length ? d.tags : [UNCATEGORIZED] })),
    );
  return cache;
}
  • d.tags ?? [...] だと空配列 [] を見逃す。?.length で、未設定と空の両方を拾う
  • 表示側(一覧・詳細・タグ集計)は何も意識しなくてよい。管理画面のデータは変わらず、ビルド時に補うだけ
  • 関連記事を作るときは、全員が持つ「未分類」で結びつかないよう、t !== UNCATEGORIZED で除外する

取得コードの読み方(メモ)

  • endpoint は、管理画面で API を作る時に決めた名前。URL は https://サービスID.microcms.io/api/v1/エンドポイント
  • getAllContents は、全件を100件ずつ繰り返し取ってくれる
  • orders: "-publishedAt" の先頭の - が降順
  • cache ??= は、空の時だけ右辺を実行して代入する。何度呼んでも API を叩くのは最初の1回

この記事の学び

  • 画像1枚=1コンテンツにしておくと、グリッド・絞り込み・ライトボックスのどれにも素直に載る
  • 絞り込みの組み合わせが増えるなら、静的に作らず、全件を出して JS で隠す
  • フィールドIDを変えたら、型・取得・表示の3か所を同時に直す
  • 補完や整形は、取得の一か所に寄せる。使う側に ?? ... を散らさない
  • 管理画面の内容を変えても、開発サーバーは cache を持っているので、反映されない時は再起動する
  • 本文の持ち方は用途で決める。小説は原稿の行き来のためにテキストエリア、設定資料は画像と書式のためにリッチエディタ、日記は書きやすさ重視でマークダウン