🚀

microCMSとAstroでお知らせ一覧作ってみた

2023/03/23に公開3

概要

microCMSでお知らせ一覧を管理し、Astroで表示するところまでやっていく。
仕事で、microCMSとLaravelを使って、お知らせ一覧を返すAPIを作るような仕様で話が進んでいて、それってAstroで十分なのでは?と思ったので、検証してみる。

APIを作成

この画面を見るまで知らなかったが、都合よくお知らせ一覧用のテンプレートがあるらしいのでありがたく利用させてもらう。


APIの作成が終わったらこのような画面になった。

適当にお知らせがほしいので、Chat GPTにお願いしてみる。

これで記事の用意はできた。

Astroで表示する

microCMSの記事を参考にしつつ設定していく。

テンプレートからプロジェクトを作成する

❯❯ yarn create astro
  tmpl   How would you like to start your new project?
         Use blog template
   deps   Install dependencies?
         Yes
    ts   Do you plan to write TypeScript?
         Yes
   use   How strict should TypeScript be?
         Strict
   git   Initialize a new git repository?
         Yes
❯❯ yarn astro add tailwind

microcms-js-sdk をインストールする

yarn add microcms-js-sdk

.env.local にmicroCMSのドメインとAPIキーを書く

VITE_MICROCMS_DOMAIN=xxxxx
VITE_MICROCMS_APIKEY=thisisapikey

Astroのテンプレートで作った場合は、 .env.local が .gitignore に入ってないらしいため、追加しておく

--- a/.gitignore
+++ b/.gitignore
@@ -16,6 +16,8 @@ pnpm-debug.log*
 # environment variables
 .env
 .env.production
+.env.local
+.env.*.local

データフェッチのロジックをコピペで作成
src/library/microcms.ts

//SDK利用準備
import { createClient, MicroCMSQueries } from "microcms-js-sdk";
const client = createClient({
  serviceDomain: import.meta.env.VITE_MICROCMS_DOMAIN,
  apiKey: import.meta.env.VITE_MICROCMS_APIKEY,
});

//型定義
export type News = {
  id: string;
  createdAt: string;
  updatedAt: string;
  publishedAt: string;
  revisedAt: string;
  title: string;
  content: string;
};
export type NewsResponse = {
  totalCount: number;
  offset: number;
  limit: number;
  contents: News[];
};

//APIの呼び出し
export const getNews = async (queries?: MicroCMSQueries) => {
  return await client.get<NewsResponse>({ endpoint: "news", queries });
};
export const getNewsDetail = async (
  contentId: string,
  queries?: MicroCMSQueries
) => {
  return await client.getListDetail<News>({
    endpoint: "news",
    contentId,
    queries,
  });
};

お知らせリストを取得してみる

diff --git a/src/pages/index.astro b/src/pages/index.astro
index 26f070d..1605c54 100644
--- a/src/pages/index.astro
+++ b/src/pages/index.astro
@@ -3,6 +3,9 @@ import BaseHead from '../components/BaseHead.astro';
 import Header from '../components/Header.astro';
 import Footer from '../components/Footer.astro';
 import { SITE_TITLE, SITE_DESCRIPTION } from '../consts';
+import { getNews } from '../library/microcms';
+
+const response = await getNews({fields: ['id', 'title']})
 ---
 
 <!DOCTYPE html>
@@ -44,6 +47,16 @@ import { SITE_TITLE, SITE_DESCRIPTION } from '../consts';
                                        >astro-blog-template
                                </a> by <a href="https://twitter.com/Charca">Maxi Ferreira</a>.
                        </p>
+                       <section>
+                               <h2>News</h2>
+                               <ul>
+                                       {response.contents.map(content =>
+                                               <li>
+                                                       <a href="http://">{content.title}</a>
+                                               </li>
+                                       )}
+                               </ul>
+                       </section>
                </main>
                <Footer />
        </body>

この記事を参考にして、ニュース一覧ページを作成
src/pages/news/page/[page].astro

---
import BaseHead from '../../../components/BaseHead.astro';
import Header from '../../../components/Header.astro';
import Footer from '../../../components/Footer.astro';
import { SITE_TITLE, SITE_DESCRIPTION } from '../../../consts';
import { getNews, News } from '../../../library/microcms';
import type { GetStaticPathsOptions } from 'astro';

export const getStaticPaths = async ({ paginate }: GetStaticPathsOptions) => {
  const pageSize = 3 // 3記事ずつにページを分割
  const response = await getNews({
    fields: ['id', 'title'],
    limit: 1000,
    orders: '-publishedAt',
  })
  return paginate(response.contents, { pageSize });
};
const { page } = Astro.props;
export interface Props {
  page: {
    url: {
      prev: string
      next: string
    }
    currentPage: number
    data: News[]
  }
}
---

<!DOCTYPE html>
<html lang="en">
	<head>
		<BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} />
	</head>
	<body>
		<Header title={SITE_TITLE} />
		<main>
      <h1>News</h1>
      <ul class="p-8">
        {page.data.map(content =>
          <li>
            <a href="http://">{content.title}</a>
          </li>
        )}
      </ul>
      <nav class="flex justify-between w-full">
        <a class="p-2 border border-slate-400 rounded-sm" class:list={[{'pointer-events-none': !page.url.prev, 'opacity-30': !page.url.prev}]} href={page.url.prev}>&lt;</a>
        <a class="p-2 border border-slate-400 rounded-sm" class:list={[{'pointer-events-none': !page.url.next, 'opacity-30': !page.url.next}]} href={page.url.next}>&gt;</a>
      </nav>
		</main>
		<Footer />
	</body>
</html>

このように3件ずつページを分けることができる。

次は、ニュースの個別ページ
src/pages/news/[id].astro

---
import BaseHead from '../../components/BaseHead.astro';
import Header from '../../components/Header.astro';
import Footer from '../../components/Footer.astro';
import { SITE_TITLE, SITE_DESCRIPTION } from '../../consts';
import { getNewsList, getNewsDetail } from '../../library/microcms';

export const getStaticPaths = async () => {
  const response = await getNewsList({ fields: ['id'], limit: 1000 });
  return response.contents.map((content) => ({
    params: {
      id: content.id,
    },
  }));
};
// Astro.paramsから各ルーティングのidを取得
const { id } = Astro.params;
const news = await getNewsDetail(id as string);
---

<!DOCTYPE html>
<html lang="en">
	<head>
		<BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} />
	</head>
	<body>
		<Header title={SITE_TITLE} />
		<main>
      <h1>{news.title}</h1>
      <div set:html={news.content}></div>
		</main>
		<Footer />
	</body>
</html>
--- a/src/pages/news/page/[page].astro
+++ b/src/pages/news/page/[page].astro
@@ -40,7 +40,7 @@ export interface Props {
       <ul class="p-8">
         {page.data.map(content =>
           <li>
-            <a href="http://">{content.title}</a>
+            <a href={`/news/${content.id}`}>{content.title}</a>
           </li>
         )}
       </ul>

これで、個別ページもできました。

できあがったコードはこちら。
https://github.com/KazukiMiyazato2021/astro-microcms

思ったよりmicroCMSもAstroも使いやすくて簡単でした。
お知らせ一覧を作る程度で、Laravelを構築するようなことにならずに済みそうです。

Discussion

KazukiMiyazatoKazukiMiyazato

コンテンツ更新時に自動更新されるように設定

  1. GitHubでパーソナルアクセストークンを作成
    https://github.com/settings/personal-access-tokens/new
    Create a repository dispatch event を読む限り、metadata:readcontents:read&write だけ権限があればよさそう。

  2. イベントタイプを追加
    repository_dispatchにtypeを設定する必要があるらしいので追加。
    https://github.com/KazukiMiyazato2021/astro-microcms/commit/a7ef899cfb13ab89bd2f8146e00557227b7fe819

  3. microCMSでWebhookを追加
    https://my-service.microcms.io/apis/news/settings/webhook

KazukiMiyazatoKazukiMiyazato

astro/imageの結果をキャッシュするように変更

- name: Restore cached images
  id: cache-images-restore
  uses: actions/cache/restore@v3
  with:
    path: node_modules/.astro/image/
    key: ${{ runner.os }}-images

- name: Install, build, and upload your site
  uses: withastro/action@v0

- name: Save cached images
  id: cache-images-save
  uses: actions/cache/save@v3
  if: always()
  with:
    path: node_modules/.astro/image/
    key: ${{ steps.cache-images-restore.outputs.cache-primary-key }}

https://github.com/KazukiMiyazato2021/astro-microcms/blob/main/.github/workflows/deploy.yml#L44-L49