microCMS & Astro で永久0円のJamstack爆速ブログを作成する丁寧なハンズオン

`アイレット株式会社 22新卒 Advent Calendar 2022` 1日目の投稿です！
社外へアウトプットする習慣を作ることを目的として、このアドベントカレンダーを企画しました:raised_hands:
同期25人で最後まで完走することを目標に頑張ります！

# 概要
このハンズオンでは、microCMS・Astro・Vercelを利用し、永久0円の個人ブログを作成します。

# 所要時間
1 時間

# 流れ & 学べること

1. **前知識** : 必要な知識を確認します。
1. **microCMS** : microCMS の会員登録 / サービスを作成します。
1. **Astro** : Astroプロジェクトを作成し、microCMSと連携します。
1. **Vercel** : Vercelにデプロイし、microCMSとのデプロイフック連携をします。

# 前知識

## なぜ 0円 なのか
このハンズオンでは、記事の管理（CMS）に microCMS、ブログのデプロイに Vercel という SaaS (Software as a Service) を利用します。
いずれのサービスにも、一定の利用量までは無料の **無料枠** が設定されており、個人ブログの利用範囲では無料枠を超えないため、無料で利用することができます。

## microCMS とは
microCMSは、国産のヘッドレスCMSです。
ヘッドレスCMSとは、Wordpressのような従来のCMSが有していた「コンテンツ管理」「HTML生成」の２つの責務を切り分け、「コンテンツ管理」の部分だけをAPIで提供するものです。
SaaSとして提供されるので、**サーバを用意する必要はありません**。
多く存在するヘッドレスCMSの中でも microCMS のメリットは、コンソールが日本語対応しており、無料枠が広いことです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227791/84cd0ca0-4d20-8d3a-9bcc-9be0e89de381.png)


## Astro とは
Astro は、2022年8月に正式リリースされたばかりの静的サイトジェネレータです。
現時点で静的サイトを生成する方法としては Next.js などの SSG が採用されることが多いかと思いますが、Astro は Next.js などと比べて**生成されるコードに含まれるJavaScriptが圧倒的に少なく、非常に高速である**ことをアピールしています。
また、コンポーネントごとに React / Vue / Svelte などを自由に組み合わせて使うことができます。（使いどころはわからないですが）
一番似ている競合は Gatsby だと思いますが、個人的には Gatsby よりかなり使いやすい印象を受けました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227791/32b6f6b0-48fc-4fde-02fc-0f74ee13b211.png)


## Jamstack とは
Jamは、JavaScript/APIs/Markup の頭文字のようです。
ググって出てくる解説では定義に揺れが見えますが、一般的に、**ヘッドレスCMSと静的サイトジェネレータの組み合わせでコンテンツを配信するアーキテクチャ**を指します。
配信されるのは静的なHTMLファイルなので、CDNにもキャッシュされ、高速に配信されます。
一方、更新のタイムラグが発生するので、ニュースサイトなどリアルタイムな更新が必要なケースには向かない場合があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227791/2e5cf2af-95ea-0c6f-1d39-c0f4dbcf9211.png)


## Vercel とは
Vercelは、Webホスティングサービスです。
GitHubと連携することで**簡単にWebアプリケーションをデプロイ**できます。
無料枠も趣味で利用するなら十分です。
デフォルトでCDNを介して配信されるので、とても高速です。
ちなみに、Next.js も Vercel が開発していて、Next.js の ISR にも対応しています。

---
ではさっそく、ハンズオンを始めましょう！:v:

# microCMS

## microCMSに登録する

microCMSの登録画面にアクセスして、メールアドレスとパスワードを入力します。

https://microcms.io/

![microCMS 登録画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227791/1a781a6a-eeb6-3ea8-5105-c11f8d29285c.png)


質問が表示されるので回答します。
![microCMS 2](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227791/a861ed19-9bd0-1905-765b-74c9697eb3ba.png)

こんな感じの画面になるので、Startを押下します。
![microCMS 3](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227791/3b0dc52c-588e-dbcd-552f-424883705c59.png)

## サービスを作成する

作成するサービス名、サービスIDが求められるので、任意の名前を入力して Create Service を押下します。
私は、サービスIDを `michinosuke-blog` としてみました。
![microCMS 4](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227791/46c62cdb-3864-f7f5-62bb-af115e636de3.png)

入力したIDが登録できると、サービスが作成されます。
![microCMS 5](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227791/8bdc126c-ec09-2057-edf3-67f3a5d51a9c.png)

## Blog APIを作成する

作成する API のテンプレートを選択する画面になるので、今回は Blog を選択します。
![microCMS 6](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227791/e49fd518-9760-68fc-b1bd-3fccbdc00fca.png)

しばらくローディングを待つと、次のような画面になります。
すでにサンプルとして、 `(Sample) First, open this article` というタイトルの記事が登録されていることがわかります。
![microCMS 7](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227791/2aad21d5-c50f-951c-f9aa-3e2b3ac1d03f.png)

## 日本語化する

左下のアイコンを押下し、「Language」 を選択して、プルダウンから日本語に変更してから `Save changes` を押下します。
![スクリーンショット 2022-11-20 4.51.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227791/504b717b-ead6-a577-3b79-242bf12b4cfe.png)

## APIを叩く

`1個のAPIキー` というタブを選択すると、次の画面に遷移します。
APIキー名 default　の APIキー を `copy` を押下して、キーをコピーします。
![スクリーンショット 2022-11-20 5.12.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227791/b4307a0f-2ea1-ddf7-28b8-06812f0e9c1c.png)

> `jq` コマンドをインストールしていない場合は、あらかじめ次のコマンドを実行してインストールしておきます。
> ```sh
> $ brew install jq
> ```

ターミナルを開き、次のコマンドを実行します。
`xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` の部分は、先ほどコピーしたAPIキーに置き換えてください。
`michinosuke-blog` の部分も、ご自身のサービスIDに置換してください。

```shell-session
$ curl "https://michinosuke-blog.microcms.io/api/v1/blogs" -H "X-MICROCMS-API-KEY:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" | jq
```

すると、以下のような jsonレスポンスを得られます。

```json
{
  "contents": [
    {
      "id": "ch25b_0q5q",
      "createdAt": "2022-11-19T19:35:59.979Z",
      "updatedAt": "2022-11-19T19:35:59.979Z",
      "publishedAt": "2022-11-19T19:35:59.979Z",
      "revisedAt": "2022-11-19T19:35:59.979Z",
      "title": "(Sample) First, open this article",
      "content": "<h2 id=\"h2cec5766ea\">Created from a blog template🎉</h2><p>Created API from blog template. <br>Excellent job🎉<br></p><h2 id=\"h72c53f91f1\">Try Preview API🚀</h2><p>Let's try Preview API first. <br> The content you submit is retrieved via API and connected to the View. <br>The Preview API allows you to see the actual API response and accelerate your development. <br><br>👇Click here first. <br><img src=\"https://images.microcms-assets.io/assets/59d6f83d54a54444818f0f93961eab8f/6ea8d5840938412b988aeea8e9b5f247/blog-template-description1.png\" alt=\"\"><br><br>When the Preview API screen opens, click<strong> Get </strong>button to try out the request.<br><img src=\"https://images.microcms-assets.io/assets/59d6f83d54a54444818f0f93961eab8f/20545be1aa97451f878f65da35332792/blog-template-description2.png\" alt=\"\"><br><br>You can see that the contents of this article can be retrieved via the API.<br><img src=\"https://images.microcms-assets.io/assets/59d6f83d54a54444818f0f93961eab8f/cfc8d8829f824d56b726c4030bdef38c/blog-template-description3.png\" alt=\"\"></p><h2 id=\"h35257771a7\"><br>What to do next? 🏃</h2><p>Build your own website using the responses you see in the Preview API.<br>microCMS retrieves content via API, so you can build screens in your favorite language and framework. <br></p><ul><li><a href=\"https://document.microcms.io/tutorial/javascript/javascript-top\" target=\"_blank\" rel=\"noopener noreferrer\">JavaScript SDK</a></li><li><a href=\"https://document.microcms.io/tutorial/nuxt/nuxt-top\" target=\"_blank\" rel=\"noopener noreferrer\">Nuxt SDK</a></li><li><a href=\"https://document.microcms.io/tutorial/gatsby/gatsby-top\" target=\"_blank\" rel=\"noopener noreferrer\">Gatsby SDK</a></li></ul><p><br>Additionally, <a href=\"https://microcms.io/features/sdk\" target=\"_blank\" rel=\"noopener noreferrer\">server-side SDKs (PHP / Go / Ruby) and mobile SDKs (iOS / Android)</a> are also available.<br><br>Please feel free to reach out to us if you have any problems or questions. <br><br>Support: <a href=\"mailto:support@microcms.io\" target=\"_blank\" rel=\"noopener noreferrer\">support@microcms.io</a><br>Frequently Asked Questions: <a href=\"https://help.microcms.io/ja/knowledge\" target=\"_blank\" rel=\"noopener noreferrer\">https://help.microcms.io/ja/knowledge</a></p>",
      "eyecatch": {
        "url": "https://images.microcms-assets.io/assets/59d6f83d54a54444818f0f93961eab8f/4d80c8ec72754b38aef5b0eceeca0035/blog-template-en.png",
        "height": 630,
        "width": 1200
      },
      "category": {
        "id": "120hydnnx7m",
        "createdAt": "2022-11-19T19:35:57.241Z",
        "updatedAt": "2022-11-19T19:35:57.241Z",
        "publishedAt": "2022-11-19T19:35:57.241Z",
        "revisedAt": "2022-11-19T19:35:57.241Z",
        "name": "Update Information"
      }
    }
  ],
  "totalCount": 1,
  "offset": 0,
  "limit": 10
}
```

なお、APIのテスト実行は、コンソール画面右上の `APIプレビュー` からも行うことができます。
![スクリーンショット 2022-11-20 6.32.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227791/2b9c0960-73c1-1092-140a-0e4b049798e0.png)


# Astro

## Astroプロジェクトを作成する

npm の create コマンドでAstroプロジェクトの雛形を作成します。
今回は pnpm 使ってみます。

```shell-session
$ pnpm create astro@latest

? Where would you like to create your new project? › astro-blog-example
✔ How would you like to setup your new project? › a personal website starter kit
✔ Would you like to install pnpm dependencies? (recommended) … yes
✔ Would you like to initialize a new git repository? (optional) … no
✔ How would you like to setup TypeScript? › Strict

╭─────╮  Houston:
│ ◠ ◡ ◠  Good luck out there, astronaut!
╰─────╯
```

Houston は メジャーリーグのヒューストン・アストロズのパロディでしょうか？

## GitHubリポジトリを作成する

Astro のインストールが終わったら、VSCode で開きます。

```shell-session
$ code astro-blog-example
```

ソース管理タブを開いて、`GitHubに公開` を押下し、GitHubにパブリックリポジトリを作成します。

![スクリーンショット 2022-11-21 0.20.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227791/536dd028-8791-5655-f0b8-5a73038bdcfd.png)

## Astroを実行してみる

Astroを実行してみます。

```shell-session
$ pnpm run dev

  🚀  astro  v1.6.10 started in 250ms
  
  ┃ Local    http://127.0.0.1:3000/
  ┃ Network  use --host to expose
```

それから、http://localhost:3000 にアクセスしてみます。
こんなページが表示されたら成功です。

![スクリーンショット 2022-11-21 0.27.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227791/1ce20e41-edd8-fa13-7e0e-ef3dee431c1d.png)

http://localhost:3000/blog にアクセスすると、サンプルの記事一覧が表示されます。
ここの記事を、microCMS で管理された記事に置き換えるのが目標です。
![スクリーンショット 2022-11-21 0.30.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227791/d2606d4b-e83d-27f2-51e3-7c67f19bafe9.png)


これは、`src/pages/blog.astro` の以下の部分で、マークダウンファイルを読み込んでいるので、これを microCMS からフェッチするように変更することで実現できます。

```ts:src/pages/blog.astro
// Use Astro.glob() to fetch all posts, and then sort them by date.
const posts = (await Astro.glob('./blog/*.{md,mdx}')).sort(
	(a, b) => new Date(b.frontmatter.pubDate).valueOf() - new Date(a.frontmatter.pubDate).valueOf()
);
```

## microCMSと連携する

プロジェクトの `package.json` と同じ階層に、`.env` ファイルを作成して、以下の内容を書き込みます。
`michinosuke-blog` はあなたが作成した microCMS のサービスIDに、`xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` の部分は、あなたが作成したAPIのキーに置き換えてください。

```sh
MICROCMS_SERVICE_DOMAIN=michinosuke-blog
MICROCMS_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

microCMS は JavaScript用SDK を公開してくれているので、インストールします。

```shell-session
$ pnpm install microcms-js-sdk
```

`src/library/microcms.ts` ファイルを作成し、以下を書き込みます。

```ts:src/library/microcms.ts
import { createClient, MicroCMSQueries } from "microcms-js-sdk";
const client = createClient({
  serviceDomain: import.meta.env.MICROCMS_SERVICE_DOMAIN,
  apiKey: import.meta.env.MICROCMS_API_KEY,
});

export type Blog = {
  id: string;
  createdAt: string;
  updatedAt: string;
  publishedAt: string;
  revisedAt: string;
  title: string;
  content: string;
  eyecatch: {
    url: string;
    height: number;
    width: number;
  };
};
export type BlogResponse = {
  totalCount: number;
  offset: number;
  limit: number;
  contents: Blog[];
};

export const getBlogs = async (queries?: MicroCMSQueries) => {
  return await client.get<BlogResponse>({ endpoint: "blogs", queries });
};
export const getBlogDetail = async (
  contentId: string,
  queries?: MicroCMSQueries
) => {
  return await client.getListDetail<Blog>({
    endpoint: "blogs",
    contentId,
    queries,
  });
};
```

それから、`src/pages/blog.astro` の上部を以下のように編集します。

```ts:src/pages/blog.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 '../config';

//microCMS呼び出し
import { getBlogs } from "../library/microcms";
const { contents: posts } = await getBlogs({ fields: ["id", "title", "publishedAt"] });
---
```

また、`posts.map` しているところも、下のように置き換えます。

```tsx:src/pages/blog.astro
<ul>
	{
		posts.map((post) => (
			<li>
				<time datetime={post.publishedAt}>
					{new Date(post.publishedAt).toLocaleDateString('ja-JP')}
				</time>
				<a href={`/blog/${post.id}`}>{post.title}</a>
			</li>
		))
	}
</ul>
```

改めて、Astroを起動して、http://localhost:3000/blog にアクセスします。

```shell-session
$ pnpm run dev
```

そして、以下のように microCMS のサンプル記事 `(Sample) First, open this article` が表示されていれば成功です！:tada:

![スクリーンショット 2022-11-21 2.20.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227791/4fbbada9-29ec-8163-2dfa-fb4dd155586e.png)

ただ、現状だと、記事タイトルをクリックしても、`404: Not found` になってしまいます。

![スクリーンショット 2022-11-21 3.09.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227791/a4ad1125-7788-2a0c-16f6-cd9a24533780.png)


プロジェクトにテンプレートで生成されているように、記事の詳細ページは `src/pages/blog/` 配下にファイルを配置することで、静的に生成することができます。

しかし、今回は microCMS に存在する投稿によって、動的にページを生成する必要があるため、変更が必要です。

`src/pages/blog/[blogId].astro` ファイルを作成し、以下の内容を書き込みます。

```tsx:src/pages/blog/[blogId].astro
---
import Layout from "../../layouts/BlogPost.astro";
import { getBlogs, getBlogDetail } from "../../library/microcms";

// 生成する記事のIDを全て取得
export async function getStaticPaths() {
  const response = await getBlogs({ fields: ["id"] });
  return response.contents.map((content: any) => ({
    params: {
      blogId: content.id,
    },
  }));
}

//記事の詳細情報を取得
const { blogId } = Astro.params;
const blog = await getBlogDetail(blogId as string);
---

<Layout content={{
    title: blog.title,
    description: '',
    heroImage: blog.eyecatch?.url ?? '',
    pubDate: new Date(blog.publishedAt).toLocaleDateString('ja-JP'),
    updatedDate: new Date(blog.updatedAt).toLocaleDateString('ja-JP'),
}}>
  <main set:html={blog.content} />
</Layout>
```

再度、http://localhost:3000/blog にアクセスし、記事タイトルをクリックしてみます。
すると、このように記事ごとの詳細ページが生成されていることが確認できます！:tada:
![スクリーンショット 2022-11-21 3.31.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227791/e204d20a-2973-9f7a-78bc-34a4312347a0.png)

これでAstro実装は終了したので、`microCMSとの連携` のようなメッセージでGitにコミットし、GitHubにプッシュしておきます。

# Vercel

## Vercel にデプロイ

Vercel にログインし、New Project を選択します。

https://vercel.com/new

`Continue with GitHub` を選択し、Astro をプッシュしたリポジトリを import します。
![スクリーンショット 2022-11-21 3.40.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227791/b18fce23-7375-4467-34cd-0fb84b6a4167.png)

任意のプロジェクト名を入力し、環境変数 `MICROCMS_SERVICE_DOMAIN` と `MICROCMS_API_KEY` を設定します。このキーと値は、`.env` ファイルに設定しているものと同じです。
`Deploy` を押下すると、ブログがデプロイされます。
![スクリーンショット 2022-11-21 3.45.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227791/84719de1-f74e-588f-3a92-d478b35e7325.png)

このような画面になれば、デプロイ成功です！:tada:
![スクリーンショット 2022-11-21 3.48.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227791/700769b2-1d98-58bd-3315-e16bbe9606b1.png)

スクリーンショットの部分を押下すると、ブログにアクセスできます。


## デプロイフックを作成

Vercelのプロジェクト詳細のページから、`Setting > Git` に遷移します。
`Deploy Hooks` という項目があるので、任意の名前と、デプロイするブランチ名を指定して、`Create Hook` を押下します。
ここでは、microCMS という名前で master ブランチをデプロイするデプロイフックを作成しています。
![スクリーンショット 2022-11-21 3.53.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227791/f4aae5cc-41eb-1fd5-f654-11f65e69c187.png)

デプロイフックURLが生成されるので、`Copy` ボタンを押下してコピーします。
![スクリーンショット 2022-11-21 3.54.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227791/51441ca7-61b7-6944-bf6b-4d2d6471bd27.png)

## microCMSにデプロイフックを設定

microCMSのコンソールにアクセスし、右上の `API設定` を押下し、`Webhook` タブを選択します。
![スクリーンショット 2022-11-21 3.59.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227791/eae78dde-b303-8592-1509-3561c876df54.png)

`＋追加` ボタンを押下すると、「サービスの選択」 ポップアップが表示されるので、`Vercel` を選択します。
![スクリーンショット 2022-11-21 3.59.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227791/63e0693b-0815-e8c6-7067-1fdf208be89a.png)

任意の名前と、先ほどコピーした Vercel のデプロイフックURLをペーストして、下部の `設定する` ボタンを押下します。
![スクリーンショット 2022-11-21 4.01.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227791/f55442e3-5db8-aee9-6a7e-47b03c075375.png)

これで、microCMS で記事を作成すると、自動で Vercel にブログが再デプロイされるようになりました！:tada:

## 記事を作成してみる

microCMS コンソール右上の `+追加` ボタンを押下します。

![スクリーンショット 2022-11-21 4.06.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227791/72f94ff5-6e4d-0922-327c-13f2bb0d5d19.png)

適当なタイトル、コンテンツ、サムネイルを設定して、右上の `公開` ボタンを押下しましょう。
それから、再度 Vercel にデプロイしたブログのURLにアクセスしてみると、新しく追加した記事が一覧に表示されているはずです！
なお、microCMSコンソールで記事を作成してからブログに反映されるまで、数十秒のラグがあります。
![スクリーンショット 2022-11-21 4.15.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227791/35e91954-1a4a-3ae8-cb12-5e6ecf345063.png)

詳細ページも生成されています。
![スクリーンショット 2022-11-21 4.18.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227791/f054454c-b50a-a85c-336e-0f4b2455197d.png)


# さいごに

おつかれさまでした！:tada: :confetti_ball:

この記事の内容は、個人ブログの他にも、

- 店舗ホームページのニュース投稿をクライアントにやってもらう
- イベントのバナーをCMSで管理する
- 利用規約を再ビルドなしで更新できるようにする

など、応用できると思います。

Lighthouseでパフォーマンス計測しましたが、さすがAstroは爆速でした！:clap:
![スクリーンショット 2022-11-21 4.54.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227791/b8e83de7-f27a-b321-b285-0bcd481c3916.png)


[アイレット株式会社 22新卒 Advent Calendar 2022](https://qiita.com/advent-calendar/2022/iret-22) 明日の記事は、[@surai](https://qiita.com/surai)くんの『Hello AWS SAM World!!』です！
お楽しみに！！