成果物
実装した機能
- 記事一覧ページ
- 記事詳細ページ
- タグ別一覧ページ
- コメント機能(記事詳細ページ内
- いいね機能(記事詳細ページ内
- p5.jsのcanvasをコンポーネント化し、UIパーツとして使用
GraphCMSとは
- 『速攻で、GraphQLでコンテンツを取り出せるAPIを作り出せるヘッドレスCMS』
- 個人利用ならば十分な無料枠 (100万APIコール・100GBアセット配信/月 無料)
- UI・UXが洗練されていて使い心地がとてもいい
- Playgroundで気楽にGraphQLのqueryの挙動を試せる
- ドキュメントやカスタマーサポートがとても充実している
GraphCMSで実際にAPIをつくるまでのフロー
今回作ったスキーマはPOST、PostComment、PostLikeの3つ
スキーマ=コンテンツに入るデータの型定義書
スキーマ間のリレーションなども簡単に定義できるが、めっちゃ複雑なリレーションとかは無理かもしれない
3.コンテンツ追加
こんな感じで定義したスキーマに応じて入力フィールドが自動でかっこよく生成されるので、この画面でコンテンツを入力してパブリッシュすると、もうAPIから叩けるようになる。今回のケースではこの画面から入力するのは記事情報だけ。記事へのコメントといいねはブログの表示画面(フロントエンド)からAPIへのリクエスト(mutation)で作成する前提。しかし、投稿されたコメントやいいねをこの画面で一覧で確認できたり管理できたりしてとても便利。
また、webhookを設定して、記事がパブリッシュされたら自動でvercelで再デプロイされるなどの設定が可能!
GraphCMS上のPlaygroundでqueryを書いてみて、意図したデータを取り出す・データを変更する処理が出来るか試してみましょう。
ここで正しいqueryが書けているか確認できたら、あとはそれをフロントエンドのアプリケーションで正しく組み込んであげるだけです!
next.jsでフロントエンドの画面をつくる
記事詳細ページ、タグ一覧ページなどはgetStaticPathsで必要なデータを取得して(記事詳細ページ[slug].tsxならばslugを取得して)、静的に生成
export const getStaticPaths: GetStaticPaths = async () => {
const { data } = await client.query({
query: gql`
query {
posts(stage: PUBLISHED) {
slug
}
}
`,
});
const paths = data.posts.map((post: any) => ({
params: {
slug: post.slug,
},
}));
return { paths, fallback: false };
};
コメント機能・いいね機能などユーザーのアクションによってデータが変わる部分のデータについては、クライアントサイドでページロード時に取得
(async () => {
const { data } = await client.query({
query: gql`
query Post($id: ID) {
posts(where: { id: $id }) {
postComment {
commentBody
commentAuthor
time
id
}
postLikes {
id
ipaddress
}
}
}
`,
variables: { id: post.id },
fetchPolicy: 'no-cache',
});
})();
GraphCMSではPublicAPIから実行できる操作の権限をコントロールすることができる。
今回は、データの読み込み(Read処理)に関しては、パブリックにして、データの削除・追加・編集に関してはToken付きのリクエストのみ許可するようにした。
このTokenは外部に流出したらマズい(データが全消しされるとかもありうる)ので、こちらのTokenを含むリクエストをフロントエンドから直接叩くのはNG
今回はデータ変更リクエストについては、Token情報の秘匿のため、Next.jsのAPI Routesから叩くようにした。
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
const { postId, commentBody, commentAuthor, time } = JSON.parse(req.body);
const { data } = await client.mutate({
mutation: gql`
mutation UpdatePost($postId: ID, $commentBody: String!, $commentAuthor: String!, $time: DateTime) {
updatePost(
where: { id: $postId }
data: { postComment: { create: { commentBody: $commentBody, commentAuthor: $commentAuthor, time: $time } } }
) {
id
postComment {
commentBody
commentAuthor
time
id
}
}
}
`,
variables: {
postId: postId,
commentBody: commentBody,
commentAuthor: commentAuthor,
time: time,
},
});
await client.mutate({
mutation: gql`
mutation PublishPostComment($postCommentId: ID) {
publishPostComment(where: { id: $postCommentId }, to: PUBLISHED) {
commentBody
}
}
`,
variables: {
postCommentId: data.updatePost.postComment[data.updatePost.postComment.length - 1].id,
},
});
res.status(200).end();
}
おまけ
今回、ブログを作って、地味に困ったのが、いいねボタンやコメント機能を実装した際、フロントエンドからリクエストを送ってデータの更新リクエストを送ってから queryから返ってくるレスポンスにリクエストのデータが反映されるまで約一分かかってしまうという時差が発生してしまうところだった。
これはGraphCMSのサーバーが基本的にキャッシュレイヤーのデータを返す仕様になっているからで、
データ更新リクエスト→キャッシュレイヤーの更新処理→レスポンスに反映
というキャッシュレイヤーの更新処理の間のギャップが最大一分あるから発生している挙動だった。
この挙動に関しては、公式ドキュメントなどにも迂回方法はのっていないが、カスタマーサポートに問い合わせたら、キャッシュレイヤーをバイパスしてデータを取りにいく設定方法を教えていただけたので、気になる方はぜひカスタマーセンターに問い合わせてみてください。このやりかたで、フロントからデータ更新リクエストを送って5秒くらいでGraphCMSからのレスポンスにも反映されるようになったので、満足いくクオリティになりました。
Next.jsでP5.jsを用いたcanvasをコンポーネント化したUIを作ってみたチャレンジに関してはまた、時間と気力ができれば、別記事で書かせていただきたいと思います。よろしくお願いします!