Prismic.ioとNext.jsでJamstackなGitHubPageを作成する
はじめに
こちらは、デジクリ Advent Calendar 2022 の4日目の記事となります。
デジクリについてはこちらのサイトをご覧ください。
業務でHeadlessCMSを触る機会があったので、HeadlessCMSやJamstackをさわる目的もかねて、長らく素のReactのままだった自分のポートフォリオサイトをタイトルにもあるとおり、Prismic.io(Headless CMS)とNext.jsで作り直しました。
今回は備忘録も兼ねて記事にします。
前提知識
Headless CMSとは
CMSとはContent Management Systemの略でコンテンツの登録・編集出来るユーザー管理・公開などさまざまな機能をもったものです。
WordPressなどが有名ですね。
Headlessとは見た目の機能がない、つまりHTMLなどを生成したりする機能がないということなので、Headless CMSはコンテンツの登録やユーザー管理は用意しているがデータの公開部分はAPIなどで自分が作ったフロントエンドと接続するようなものになっています。
今回使うPrismic.ioもHeadless CMSの仲間で、他には日本産のmicroCMSなどが有名です。
PrismicCMSは編集ユーザーが一人のプランの場合、無料でかなり制限が緩いので今回採用しました。

SSGとは
SSGとはStatic Site Generationの略でビルド時にサイト内のコンテンツ情報も含めて静的なHTMLを吐き出すアーキテクチャのことです。
GitHub Pagesは基本的にこの方法かCSR(Javascriptでユーザーブラウザ内でHTMLを生成)する方法に対応しています。
今回はNext.jsのSSG機能を使っていきます。
Jamstackとは
事前にデータを埋め込んだ静的なHTMLをCDNなどで配信し、動的なコンテンツはAPI経由でJSに動的にレンダリングさせるアーキテクチャです。
Prismic.ioでプロジェクト作成
prismic.ioに行き

GitHubでサインアップします。

登録ができたらダッシュボードに行き、With Next.jsを選択して

テンプレートはBlank Projectを選択します。
結構良さげなテンプレートが沢山あるので、このなかから選んでも良いかもしれませんね。

リポジトリ名(GitHubのリポジトリではない)などを決めて作成しましょう。

これで一端Prismic.io側の操作は終わりです。次はNext.jsのプロジェクトを作成していきます。

Next.jsでプロジェクト生成
create-next-appで生成していきます。ESLintを使うか聞かれますがYesと答えておきましょう。
yarn create next-app --typescript mogami-test
プロジェクトフォルダに入り、prismicが使えるようにしていきます。
cd mogami-test
npx @slicemachine/init --repository mogami-test
yarn add @prismicio/next
プロジェクトフォルダ直下にprismicio.jsを作成します。
import * as prismic from '@prismicio/client'
import * as prismicNext from '@prismicio/next'
import sm from './sm.json'
/**
* The project's Prismic repository name.
*/
export const repositoryName = prismic.getRepositoryName(sm.apiEndpoint)
// Update the routes array to match your project's route structure
/** @type {prismic.ClientConfig['routes']} **/
const routes = [
{
type: 'homepage',
path: '/',
},
{
type: 'page',
path: '/:uid',
},
]
/**
* Creates a Prismic client for the project's repository. The client is used to
* query content from the Prismic API.
*
* @param config {prismicNext.CreateClientConfig} - Configuration for the Prismic client.
*/
export const createClient = (config = {}) => {
const client = prismic.createClient(sm.apiEndpoint, {
routes,
...config,
})
prismicNext.enableAutoPreviews({
client,
previewData: config.previewData,
req: config.req,
})
return client
}
SliceMachineで型定義する
SliceMachineというものでHeadlessCMSに投稿するコンテンツの型などをつくることで、Prismic.ioの自分のプロジェクトに反映したり、後で利用するprismic clientのレスポンス型を生成するすることができます。
SliceMashineを起動
以下のコマンドで起動します
yarn slicemachine

localhost:9999にアクセスします。
Create oneを押してコンテンツの型を作っていきます。

今回はプレイしたゲームの一覧を表示するページを作るのでゲームの型を作っていきます
同じゲーム型のものを沢山登録するのでRepetable typeを選択します
TypeにはGameを入力します。

Add a new fieldを押して要素を追加します。

要素はいろいろ種類があり、画像要素などを選ぶとprismic側にアップロードした画像を選択できます。

今回はKeyTextを選び名前をnameにします。

同様にKeyTextでdescriptionとImageでimageを追加します。

Save to File Systemを押します。

Changeタブに移動し、Push Changeで変更点をprismic.ioの方に適用します。

Prismic.ioにデータを書き込む
prismic.ioのダッシュボードに戻りリポジトリを開きます
開いたら右上の鉛筆ボタンから編集画面に入ります

nameとdescriptionに適当にあたいを入れます。
imageにも適当な画像を入れます。

右上のSaveを押し、次にPublishを押し、再度Publishを押してコンテンツを公開状態にします。

同じ手順で何個か登録をします。

Next.jsで表示部分を作る
prismicio.jsを編集します。
const routes = [
{
+ type: 'game',
- type: 'homepage',
path: '/',
},
- {
- type: 'page',
- path: '/:uid',
- },
]
index.tsxを以下のようにします
import { createClient } from "../prismicio";
import { GetStaticProps } from "next";
import { GameDocument } from "../.slicemachine/prismicio";
type Props = {
games: GameDocument[];
};
export default function Home({ games }: Props) {
return (
<div>
{games.map((g) => (
<div key={g.id}>
<h2>{g.data.name}</h2>
<img
src={g.data.image.url!}
alt=""
style={{ maxWidth: "100%", backgroundColor: "white" }}
/>
<p>{g.data.description}</p>
</div>
))}
</div>
);
}
export const getStaticProps: GetStaticProps = async ({ previewData }) => {
const client = createClient({ previewData });
const games = await client.getAllByType("game");
return {
props: { games },
};
};
動作確認してみます
yarn dev
localhost:3000にアクセスすると投稿したコンテンツが表示されていることが分かります。

yarn exportができるようにpackage.jsonを書き換えます
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
+ "slicemachine": "start-slicemachine",
- "slicemachine": "start-slicemachine"
+ "export": "next export"
},
静的なHTMLファイルを生成してみます。
yarn build
yarn export
うまくいきましたね

GitHubPage生成用のGitHubActionを整備する
mainブランチを更新時に勝手にyarn exportして静的なHTMLファイルを作りGitHub Pageに適用するGitHub Actionを作成します。
作成したGitHub上のリポジトリに行き、Setting内のPagesにてSourceをGitHub Actionsに切り替えます。
Next.jsのプロジェクトとして認識しているのでそのままConfigureボタンを押すことでGitHub Actionのymlファイルが生成されます

忘れずにStart commitを押してコミットします。

コミット後Actionが動いていることが確認できます。

Prismic.io更新時にGitHubActionが動くようにする
このままではPrismic.io側で何かコンテンツを新しく増やしたり変更したときにActionが走らず不便です。
そこでPrismic.ioのWebhook経由でGitHub Actionを動かせるようにします。
ただ、Prismic.ioのWebhook機能で呼び出せる形式にGitHub ActionのAPIは対応していないので間にGASを挟みます。
GitHubのPersonalAccessTokenを取得する
ここからPersonalAccessTokenを取得します。
Generate New Token(Classic)を押します

期限を設定(あまりよろしくないですが無期限でつくります)してscopesとしてrepoを設定します

GASでWebアプリを作成する
取得したTokenで以下のようなGASを作成しWebアプリとしてデプロイします
function doPost(e) {
var data = {
"event_type": "update-cms"
};
var token = "取得したGitHubのAccessToken"
var headers = {
"Accept": "application/vnd.github.v3+json",
"Authorization": "token " + token
};
var options = {
"headers": headers,
"Content-Type": "application/json",
"method": "post",
"payload": JSON.stringify(data)
};
UrlFetchApp.fetch("https://api.github.com/repos/ユーザー名/リポジトリ名/dispatches", options);
}
GitHubActionのトリガーを追加する
さきほど作成したnextjs.ymlにAPIからトリガーを受け取れるように編集します。
on:
# Runs on pushes targeting the default branch
push:
branches: ["master"]
+ repository_dispatch:
+ types:
+ - update-cms
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
prismic.ioにwebhookを設定する
prismic.ioのプロジェクトページのSettingのWebhook設定ページに行きます

Create a webhookボタンを押して新しいWebhookを設定します。
名前にはGitHubAction、URLにはGASのプログラムをWebアプリとしてデプロイした先のURLを指定します。
それ以外の設定はそのままでAdd this webhookを押します
以上でprismic.ioとGitHubActionとの連携は完了です。
試しにコンテンツを増やしたり編集して、GitHubActionが走るか確認してみましょう
画像のようにActionの一覧にupdate-cmsが表示されれば成功です。

おわりに
今回、この記事を書くにあたってまだprismic.io自体は3日ほどしか触っておらずまだ公式リファレンスもろくに読んでいません。もし記事に致命的な問題等あればご指摘いただけると幸いです。
Discussion