Vue (vite-ssg) のサイトで microCMS を試してみた

## はじめに

今回は標題のとおり [Vue](https://ja.vuejs.org/) ([vite-ssg](https://github.com/antfu-collective/vite-ssg)) のサイトで [microCMS](https://microcms.io/) を試してみました。
[Nuxt](https://nuxt.com/) や [Next.js](https://nextjs.org/) の SSG かつ microCMS を使った例は度々見かけますが今回は以下の vite のツールを用いて SSG を行っている Vue の Web ページ での実施です。

https://github.com/antfu-collective/vite-ssg

※[microCMSでこんなことができた！あなたのユースケースを大募集 by microCMS Advent Calendar 2024](https://qiita.com/advent-calendar/2024/microcms) の25日目が空いていたので後から参加してみました🙏 (25日23時頃💨)

## 課題

今回の対象の Web ページは弊社のホームページです。

https://nightlamp.dev/

技術的な部分では `Vue3 (vite-ssg)` `Vuetify` `Cloudflare Pages` `GitHub Actions` あたりを用いて制作・デプロイを行っております。
**ホームページを更新するためにはリポジトリ内の Markdown ファイルを更新して commit して push して PR 作って main へマージする手続きが必要な上、取引先のカルーセルのカードを編集したり増やしたりするには Vue のテンプレートを編集する必要がありました。**
今回は microCMS を導入することで `コンテンツを編集` → `公開` の 2 ステップへ短縮することを目指します。

### 余談

実は今回の課題の解決のために別のページでも使っている [SSSAPI](https://sssapi.app/) の利用も検討していましたがスプレッドシート上で長い Markdown テキストを編集するのはなんとなく嫌だったのでもっとホームページの管理に適したサービスを探すことにしました。（でも Google Workspace を使っている会社とかはとくに使い勝手の良いサービスだと思います👍️）

https://sssapi.app/

## 導入

### [1/6] API スキーマを決める

とりあえず一回のリクエストでホームページの本文と取引先の掲載情報を取得できるようにしました。
(`kind` が `md` なら本文、`carousel` なら取引先の掲載情報)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133081/114c989c-eed6-beb2-986f-0124a25a7eb0.png)

### [2/6] コンテンツを投稿しまくる

現状のホームページを見ながらひたすらコピペ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133081/dfaaecfe-e10a-4dcf-8f5c-3e24f567aeac.png)

### [3/6] ページのビルド前に `microcms-js-sdk` で内容を取得するように改修

公式の SDK があります。

https://github.com/microcmsio/microcms-js-sdk

取り急ぎ概ねこんな感じで取得して手元へ置けるようにしました。

```env
#
# [開発用の env ファイル]
# https://microcms.io
#

# https://XXXX.microcms.io の XXXX 部分
MICROCMS_SERVICE_DOMAIN=""

# https://XXXX.microcms.io/api-keys
MICROCMS_API_KEY=""

# https://XXXX.microcms.io/apis/{endpoint}
MICROCMS_ENDPOINT=""
```

```typescript
import 'dotenv/config';
import fs from 'fs';
import { createClient } from 'microcms-js-sdk';

const OFFICIAL_HP_MD_PATH = './src/plugins/md2vue/md/Desc.md';
const MICROCMS_TMP_JSON_PATH = './tmp-microcms.json'; // .gitignore
const MICROCMS_SAVED_JSON_PATH = './saved-microcms.json';

// https://xxxx.microcms.io/api-keys
const MICROCMS_SERVICE_DOMAIN = (process.env.MICROCMS_SERVICE_DOMAIN || '').trim();
const MICROCMS_API_KEY = (process.env.MICROCMS_API_KEY || '').trim();
const MICROCMS_ENDPOINT = (process.env.MICROCMS_ENDPOINT || '').trim();

const client = createClient({
  serviceDomain: MICROCMS_SERVICE_DOMAIN,
  apiKey: MICROCMS_API_KEY,
});

// ↓ build 前に呼んでもらう
export const build = async () => {
  if (fs.existsSync(MICROCMS_TMP_JSON_PATH)) {
    return;
  }
  fs.writeFileSync(MICROCMS_TMP_JSON_PATH, '');

  try {
    const res = await client.getAllContents({
      endpoint: MICROCMS_ENDPOINT,
    });
    console.log('[MicroCMS] data fetched successfully.');

    // md ファイルを更新
    fs.writeFileSync(OFFICIAL_HP_MD_PATH, res.find((item) => item.kind[0] === 'md').content, 'utf8');

    // JSON ファイルに保存
    fs.writeFileSync(
      MICROCMS_SAVED_JSON_PATH,
      JSON.stringify(
        res.filter((item) => item.kind[0] === 'carousel'),
        null,
        2,
      ),
      'utf8',
    );
  } catch (error) {
    // 失敗した場合は直近で commit された MICROCMS_SAVED_JSON_PATH を参照する (FIXME: もしかしたらビルドプロセスを失敗させた方が安全かもしれないので暫く運用してみて判断)
    console.error(error);
  } finally {
    fs.unlinkSync(MICROCMS_TMP_JSON_PATH);
  }
};
```

### [4/6] Vue ファイルも改修

前述にて自動で取得・保存されるようになった microCMS のレスポンスを参照し `v-for` でカルーセルカードを並べます。
（もともとは `<CarouselCard>` のコードが取引先の数だけたくさん記述されてました）

```vue
<script setup lang="ts">
// 略

// carousel images
import savedMicrocms from '@/../saved-microcms.json';

// 略
</script>

<template>
  <v-app>
    <v-main class="d-flex flex-column">
      <!-- 略 -->
      <CarouselCard v-for="carousel of savedMicrocms" :key="carousel.id">
        <template #logo>
          <img
            :alt="
              'carousel_img_alt' in carousel
                ? carousel.carousel_img_alt
                : carousel.carousel_name
            "
            :src="getCarouselImgSrc(carousel.carousel_img)"
            draggable="false"
          />
        </template>
        <template #title>
          <a :href="carousel.carousel_url" target="_blank">
            {{ carousel.carousel_name }} <fa-icon icon="fa-external-link" />
          </a>
        </template>
        <template #description>
          {{ carousel.content }}
        </template>
      </CarouselCard>
      <!-- 略 -->
    </v-main>
  </v-app>
</template>
```

ホームページ側の主な改修はこれで以上です。
次はデプロイ周りを見直します。

### [5/6] Cloudflare Pages に microCMS の環境変数を設定

「設定」タブをスクロールすると「変数とシークレット」があるので microCMS の API キーなどを設定します。
(env ファイルのペースト&自動でパースに対応しているので **3** のサンプル env ファイルをペーストすると手軽です。)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133081/9c2b3d67-38e3-0398-3a81-68160e58bf48.png)

### [6/6] デプロイフックを作成し microCMS と連携

1. **5** の画像と同じ設定画面からデプロイフックを作成し POST の URL を手元にコピーしておきます
2. microCMS にて 「API 設定」→「Webhook」→「＋追加」の順で操作することで Cloudflare Pages などのサービス選択ダイアログが開くので Cloudflare Pages を選びます
    - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133081/caae9888-6fbb-fc14-0560-36c6b8c16192.png)
3. 先ほどコピーしたデプロイフック URL をペーストし、その他(発火するタイミングなど)お好みで設定を行います (最後に「変更」ボタンを押す)
    - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133081/187ca781-8fe1-6434-41ad-2ae3b0bf3230.png)

以上、主にこれらの 6 つのステップを得た後、 microCMS 上にてコンテンツを更新することで Cloudflare Pages 上のホームページを更新できるようになりました :tada: 

## 結果

1. microCMS でコンテンツを編集し「公開」を押す
    - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133081/955b649c-d2fa-9256-738f-f2c8fcd9f23e.png)
2. CF 上でビルドが走ります
    - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133081/79a48fa7-1d7d-07aa-3e7b-98124dd3a718.png)
    - デプロイが完了するまで待機
        - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133081/6e9dc902-e5f4-ffdd-6a9c-1663bfdb7ce7.png)
3. 内容が反映することを確認 :tada: 
    - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133081/48048512-6e07-89ee-42fb-d500e73b08c2.png)

良さそうなので暫くプレビュー環境で使ってみて問題なさそうであれば本番へ反映していこうと思います 🥳

### さいごに

手軽にいわゆる「編集画面」を用意することができ SSG のまま利用できて嬉しかったです 💪

今後はヘッダーのアニメーション(Ascia という素敵なフレームワークで作った)の定義 (json) も同様に少ないステップで更新(差し替え)できるように改修できればなと思います 💪 💪

https://github.com/reikx/ascia

- この部分
    - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133081/de036f12-4d2a-46b4-654b-991f76f22153.png)

ここまでご高覧ありがとうございました :bow: