2022-12-21

Astro と Craft CMS で Jamstack 構成をためしてみる

これは Craft CMS Advent Calendar 2022 21日目の記事です。


Astro と Craft を組み合わせて Jamstack っぽくするのを試してみた。

Astro | Build faster websites
https://astro.build/

Astro にするか Sveltekit にするかというのもあったんだけど、Astro に GraphQL のサンプルがのってたので、とりあえず Astro を触ってみるという目的ありきで。

Data Fetching 🚀 Astro Documentation
https://docs.astro.build/en/gu...

Sveltekit も fetch で問題無く使えるはずだけど。

CMSとの組み合わせは加藤さんがかかれた MT の DataAPI を使って作る方法とかがわかりやすいと思う。

Astro.js と Movable Type Data API を使用して Jamstack な Blog を作ってみる | WWW WATCH
https://hyper-text.org/archive...

Astro をセットアップしつつ、今のブログが Remix で作ったのでその時に入れた Tailwind とか daisyui とかの設定を入れていく

npm create astro@latest

cd ./advent_craft_astro

npm run dev

npx astro add tailwind

何かしらの案件では Node.js つかってるわけだからローカルでの開発環境構築というところでは、やりやすいような気はする。

トップページ

src/pages/index.astro を用意してトップページを作成していく

---
import Layout from '../layouts/Layout.astro';

const response = await fetch("https://example.com/api",
	{
		method:'POST',
		headers: {
			'Content-Type':'application/json',
			'Authorization': 'hogehoge',
		},
		body: JSON.stringify({
			query: `
				query {
					entries(limit:50,section:"article",siteId:1,typeId:1){
						title
						id
						url
						uri
						slug
						postDate @formatDateTime(format: "Y-m-d")
						... on article_article_Entry{
							contentTag{
								title
								id
							}
						}
					}
				}
			`,
	}),
})

const json = await response.json();
const entries = json.data.entries

---

<Layout title="mersy note by Astro">
	<div class="container mx-auto">
		<div class="pt-16 lg:pt-20">
			<div class="max-w-7xl mx-auto px-4 sm:px-6 md:px-8">
				<h1 class="text-2xl font-semibold">mersy note 記事一覧</h1>
			</div>
			<div class="max-w-7xl mx-auto px-4 sm:px-6 md:px-8">
				<ul>
					{entries.map((entry) =>
					<li>
						<div>{entry.postDate} {entry.contentTag.map((tag) =>
							<a href={`/tag/${tag.title}`} prefetch="intent" class="badge badge-outline rounded-none m-2">
							{tag.title}
							</a>
							)}</div>
						<a href=`/article/${entry.slug}` class="underline">{entry.title}</a>
					</li>
					)}
				</ul>
			</div>
		</div>
	</div>
</Layout>

エンドポイント周りの情報とかはちゃんと .env ファイルにまとめる方がよい(横着。

Layout
のコンポーネントとかがある前提。

記事一覧を50件取ってきて後は map で回す。

タグ一覧ページ

タグ一覧ページもタグの一覧を取ってきてmap でまわすかんじで。

取りあえずデータ取るところだけ抜粋。

const response = await fetch("https://example.com/api",
	{
		method:'POST',
		headers: {
			'Content-Type':'application/json',
			'Authorization': 'hogehoge',
		},
		body: JSON.stringify({
			query: `
				query {
					tags(siteId:1){
						title
						id
					}
				},
			`,
		})
	})

const json = await response.json();
const tags = json.data.tags

詳細ページやタグごとの記事一覧ページはそれぞれURLが動的に変わる。   

Routing 🚀 Astro Documentation
https://docs.astro.build/en/co...

URLの一覧を用意しておく必要がある。

詳細ページ

src/pages/article/[slug].astro を用意して、コードの取得部分。

export async function getStaticPaths(){
    const response = await fetch("https://example.com/api",
        {
            method:'POST',
            headers: {
                'Content-Type':'application/json',
                'Authorization': 'hogehoge',
            },
            body: JSON.stringify({
                query: `
                    query {
                        entries(limit:3000,section:"article",siteId:1,typeId:1){
                            slug
                        }
                    }
                `,
            }),
        })
    const json = await response.json();
    const entries = json.data.entries
    return entries.map((entry) => ({
        params: {
            slug: entry.slug,
        }
    }));
}
const { slug } = Astro.params;

const response2 = await fetch("https://example.com/api",
    {
        method:'POST',
        headers: {
            'Content-Type':'application/json',
            'Authorization': 'hogehoge',
        },
        body: JSON.stringify({
            query: `
                query($slug: [String]) {
                    entry(siteId:1,section:"article",slug:$slug){
                        title
                        id
                        url
                        uri
                        slug
                        〜〜〜略〜〜〜
                        }
                    }
            }`,
            variables: {
                "slug": slug,
            },
        }),
    })

const entry2 = await response2.json();
const entry = entry2.data.entry

ここは一回の取得の中で、 props に渡すようにすればいいっぽいな。
無駄過ぎ。

タグアーカイブも同様にタグの一覧を取得して、一覧用のデータも取り出す。

取りあえずそんな感じでローカルでトップページ、詳細ページ、タグ一覧ページ、タグアーカイブが生成できた。

ビルド時間の確認

試しにビルドしてみるとこのくらいの時間がかかっている。

03:09:07 PM [build] 2910 page(s) built in 438.96s

前述の通りデータの取得をダブってやってるとかがあるから無駄もかなり多そう。

ビルドのパフォーマンスを重視するなら Movable Type とか PowerCMS の方がよさそう。

静的サイトジェネレータ(SSG)との再構築 (ファイル生成) 時間比較 | PowerCMS X ブログ
https://powercmsx.jp/blog/ssg-...

PowerCMS X だとモデルの作成とテンプレートの作成がだいぶ柔軟にわかれてるので設計する人にも扱いやすいはず。

Cloudflare Pages にデプロイ

テンプレート類を GitHub にコミットしておけば、Cloudflare Pages でも簡単に配信できる。

Cloudflare Pages からプロジェクトを作る際に、該当のリポジトリを選択する。

20221221 065848

後の設定はほぼデフォルト。

Deploy your Astro Site to Cloudflare Pages 🚀 Astro Documentation
https://docs.astro.build/en/gu...   

ドキュメントにもあるように Environment variables に Node のバージョンの指定だけは必要。

ビルド時間はローカルとほぼ変わらずだけど、ビルドして終わればページが表示される。

https://advent-craft-astro.pag...

こういう所はこの構成が楽と思える所、開発体験の良さなのかもしれない。


サービスに乗っかる安心さとかのメリットと、カスタマイズできないところがあるとかのデメリットなど、判断基準はプロジェクト、組織によりけりだけれども。
導入は簡単そう。

とはいえ、 middleware とかあれこれやろうとすると Astro だと物足りないとかあるのかなぁ。
全然深く触れていないからわかっていないところがほとんどだけど。

色々と触っていって経験はしておかないと。
やはり感じるJS力不足(何もやってないから当然)。