💡

Astro と Spearly を使ったボイラープレート 🚀

2022/12/12に公開

Astro とは?

Astro は2022年8月に公式1.0がリリースされた比較的新しい静的サイトジェネレーターです。Eleventy や Gats.by などと同じですが、利用できるUIフレームワークに縛りが無いのが特徴です。
Vue なり、React なり好きな UI フレームワークを選んで開発できます。また、極力 JavaScript を少なく実装が出来るのも特徴です。

公式のドキュメントが充実しているのでそちらを参照してください。
https://docs.astro.build/ja/getting-started/

Spearly CMS とは?

国産のヘッドレスCMSです。
ブログなどの利用であれば基本無料で使えて、基本的なヘッドレスCMSの要素をすべて備えていて RestAPI と記事更新時の Webhook をサポートしているため、今回のブログ記事で利用してみます。
埋め込みタグも用意されていて、簡単に組み込みが可能なみたいですが、今回は純粋なヘッドレスCMSとして利用します。

公式ページからアカウント登録してコンテンツを作成してください。
https://cms.spearly.com/

ドキュメントは公式ページにあるので、そちらを参照してください。
https://docs.spearly.com/

TL;DR : ボイラープレート

簡単に利用するために最低限のボイラープレートを用意しました。

以下のステップで試せます。

  1. SpearlyCMS のアカウント登録
  2. SpearlyCMS での最小限のコンテンツの作成
  3. SpearlyCMS の API キー取得
  4. 以下のレポジトリをコピーして説明に従ってデプロイ
    https://github.com/mantaroh/spearly-astro-boilerplate

Astro プロジェクトの作成

公式ドキュメントの自動 CLI で Astro をインストールを参考にして、プロジェクトを作成します。
全てデフォルトで良いと思います。今回のボイラープレートは以下の設定でプロジェクトを作成しています。

$ yarn create astro
✔ Where would you like to create your new project? … spearly-astro-boilerplate
✔ How would you like to setup your new project? › a few best practices (recommended)
✔ Template copied!
✔ Would you like to install npm dependencies? (recommended)yes
✔ Packages installed!
✔ Would you like to initialize a new git repository? (optional)yes
✔ How would you like to setup TypeScript? › Strict
✔ TypeScript settings applied!

Astro プロジェクトの実行 🚀

プロジェクトを作成したら、npm run dev すればサンプルテンプレートが実行できます。

ブログの作成

では実際にブログを構築するための手順を進めていきます。

API キーの環境設定ファイルの追加

SpearlyCMS からデータを取得するために必要な API キーを環境設定ファイル (.env)に追加します。
API キーは Spearly のチーム設定から取得できます。

プロジェクトのルートディレクトリに以下の .env ファイルを追加してください。

SPEARLY_API_KEY=<<API キー>>

依存関係をインストールする

今回、Spearly のデータをフェッチするために @spearly/sdk-js を利用します。

$ npm install @spearly/sdk-js

次に src/lib/spearly.ts というファイルを以下のように作成します。

import { SpearlyApiClient } from "@spearly/sdk-js";

const API_DOMAIN = "https://api.spearly.com"

export const spearlyApiClient = 
  new SpearlyApiClient(import.meta.env.SPEARLY_API_KEY, API_DOMAIN)

これで、spearly.ts をロードしたモジュールから Spearly クライアントを利用することが出来ます。

ブログエントリーページを作成する

まずは、ブログの一覧を表示するエントリーページを作成します。

src/pages/index.astro は、トップページになるので、このファイルを修正してブログ一覧を表示させます。

以下のように index.astro を変更します。

---
import { spearlyApiClient } from "../lib/spearly"

const contents = await spearlyApiClient.getList("blog")
const posts = contents.data.map(c => {
    const title = c.attributes.fields.data.find(f => f.attributes.identifier === "title")
    const description = c.attributes.fields.data.find(f => f.attributes.identifier === "description")
    const date = c.attributes.fields.data.find(f => f.attributes.identifier === "date")
    const image = c.attributes.fields.data.find(f => f.attributes.identifier === "image")
    const alias = c.attributes.contentAlias
    return {
        title: title?.attributes.value,
        alias,
        description: description?.attributes.value,
        date: date?.attributes.value,
        image: image?.attributes.value
    }
})
---
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My Blog</title>
</head>
<body>
    <h1>My Blog</h1>
    <ul>
      {posts.map((post) => (
        <li>
          <img src={`${post.image}`} alt={`${post.description}`} />
          <a href={`/posts/${post.alias}/`}>
            <h2>{post.title}</h2>
          </a>
          <time>{post.date}</time>
          <p>{post.description}</p>
        </li>
      ))}
    </ul>
</body>
</html>

astro は --- で区切られた最初にデータ構造を表す JavaScript を記載して、2つ目のブロックでは表示する HTML を記載します。

コードを細かく説明すると3つのブロックに分かれてます。

データフェッチ部分

import { spearlyApiClient } from "../lib/spearly"

const contents = await spearlyApiClient.getList("blog")

この部分では、作成したクライアントを利用して、getList() を利用してコンテンツデータを取得しています。
第1引数に渡しているものは、SpearlyCMS で作成したコンテンツ名です。

データ加工部分

const posts = contents.data.map(c => {
  const title = c.attributes.fields.data.find(f => f.attributes.identifier === "title")
  const description = c.attributes.fields.data.find(f => f.attributes.identifier === "description")
  const date = c.attributes.fields.data.find(f => f.attributes.identifier === "date")
  const image = c.attributes.fields.data.find(f => f.attributes.identifier === "image")
  const alias = c.attributes.contentAlias
  return {
    title: title?.attributes.value,
    alias,
    description: description?.attributes.value,
    date: date?.attributes.value,
    image: image?.attributes.value
  }
})

ここでは、取得したデータの配列を HTML 表示で扱いやすいように配列を生成しなおしています。
この記事では、title / description / date / image を持っているため、それに合わせて配列を生成しています。また、ブログには必ずエイリアスという固有の文字列を設定できます(記事記載時に未指定の場合は、独自に降られたコンテンツ番号が入る)。このエイリアスを記事詳細ページへのリンクとして利用するために配列へ設定しておきます。

表示部分

      {posts.map((post) => (
        <li>
          <img src={`${post.image}`} alt={`${post.description}`} />
          <a href={`/posts/${post.alias}/`}>
            <h2>{post.title}</h2>
          </a>
          <time>{post.date}</time>
          <p>{post.description}</p>
        </li>
      ))}

この部分は、先ほど生成したデータをHTMLに描画させています。
データ加工時に配列に入れておいた、記事固有のエイリアスを記事詳細ページとしてリンクに設定しています。

ブログ記事詳細ページを作成する

次にブログの記事を表示するページを作成します。

先ほどエントリーページで記事詳細ページのリンク先を /post/${post.alias} と設定したので、初めてのブログ「first-blog」を作ったら /posts/first-blog にアクセスしたらブログ記事が表示されるように設定します。

Astro では ブラケット[] を利用して動的ルーティングを設定することが出来るので、今回この機能を利用します。(Astro Docs:ルーティング)

src/pages/posts/[alias].astro という以下のファイルを作成します。

---
import { spearlyApiClient } from "../../lib/spearly"; 

export async function getStaticPaths() {
    const contents = await spearlyApiClient.getList("blog")
    const pages = contents.data.map((content => {
        const title = content.attributes.fields.data.find(f => f.attributes.identifier === "title")
        const description = content.attributes.fields.data.find(f => f.attributes.identifier === "description")
        const date = content.attributes.fields.data.find(f => f.attributes.identifier === "date")
        const image = content.attributes.fields.data.find(f => f.attributes.identifier === "image")
        const body = content.attributes.fields.data.find(f => f.attributes.identifier === "body")
        const alias = content.attributes.contentAlias
        return {
            params: { alias },
            props: {
                title: title?.attributes.value,
                description: description?.attributes.value,
                date: date?.attributes.value,
                image: image?.attributes.value,
                body: body?.attributes.value,
            },
        }
    }))
    return pages
}
const { title, description, date, image, body } = Astro.props
---
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>{title}</title>
    </head>
  <body>
    <img src={`${image}`} alt={`${description}`}>
    <h1>{title}</h1>
    <time>{date}</time>
    <article set:html={body} />
  </body>
</html>

動的ルーティングの場合、記載が特殊で getStaticPaths() を利用します。これは名前付きパラメーターと呼ばれるもので、[alias].astro のように名前を付けたパラメータを定義してあげることで、Astro がルーティングを作成してくれます。
今回は、エイリアス名をパラメータとして Astro がルーティングを生成してくれます。

このコードも大きく分けて3つのブロックに分かれます。

データフェッチ部分

ここはエントリーページと同じなので説明は割愛します。

データ加工部分

    const pages = contents.data.map(content => {
        const title = content.attributes.fields.data.find(f => f.attributes.identifier === "title")
        const description = content.attributes.fields.data.find(f => f.attributes.identifier === "description")
        const date = content.attributes.fields.data.find(f => f.attributes.identifier === "date")
        const image = content.attributes.fields.data.find(f => f.attributes.identifier === "image")
        const body = content.attributes.fields.data.find(f => f.attributes.identifier === "body")
        const alias = content.attributes.contentAlias
        return {
            params: { alias },
            props: {
                title: title?.attributes.value,
                description: description?.attributes.value,
                date: date?.attributes.value,
                image: image?.attributes.value,
                body: body?.attributes.value,
            },
        }
    })
    return pages;

ここもエントリーページとほぼ同じですが、今回は本文である「Body」をデータとして配列に入れています。
また、getStaticPath() にエイリアスをパラメータとして返して、ページのデータは props オブジェクトに格納して返してあげます。

表示部分

const { title, description, date, image, body } = Astro.props;
---
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>{title}</title>
    </head>
  <body>
    <img src={`${image}`} alt={`${description}`}>
    <h1>{title}</h1>
    <time>{date}</time>
    <article set:html={body} />
  </body>
</html>

ページ表示部分はエントリーページとは少し違って、加工データーで格納した props からデータを取り出して、それを HTML に表示させています。

テスト実行

このプロジェクトをテストするには npm run dev を実行することで http://localhost:3000 からページを見ることが出来ます。

デプロイ

ここまでできたので、次にデプロイをしてみます。

Spearly CMS には標準で Spearly CLOUD というデプロイの仕組みがありますが、今回は Netlify を利用してみます。

デプロイ設定

Netlify にログインしてデプロイ設定をします。チームの概要ページから「Import from Git」を選択します。 

その後、今回作ったレポジトリを選択します。

APIキーの設定(環境変数)

最後に、デプロイ設定ですが、ここで API キーを環境変数に入れ込みます。「Show Advanced」ボタンをクリックして「New Variable」ボタンを押して環境変数を設定します。

設定後、「Deploy site」ボタンを押してデプロイします。

しばらくすれば、デプロイが終わりサイトのプレビューが出来るようになります。(なぜか初回だけデプロイ失敗するので、1回目失敗したら手動でデプロイしなおしてください。)


WebHook 設定

このままだと、最初の一回だけで記事を更新してもデプロイされません。

そのため、SpearlyCMS の WebHook 設定をして記事を更新するたびにデプロイされるように設定します。

Netlify Webhook の取得

Netlify の設定ページで Webhook を生成してコピーします。

Spearly CMS の Webhook 設定

SpearlyCMS ではコンテンツタイプごとに Webhook を設定できます。先ほどの生成した Webhook URL を SpearlyCMS のコンテンツに設定します。

これで、記事を更新するたびにデプロイが実行され常に最新のブログ記事が掲載されるようになります。

まとめ

  • SpearlyCMS+Astro を使えばブログサイトの構築が簡単に行えます。
  • SpearlyCMS の記事更新を検出して自動でデプロイされるサイトの運用ができます。

Discussion