astro-notion-blogはサーバーサイドレンダリング(SSR)を使用していないため、ホスティングサービスを選ばず様々なクラウドやレンタルサーバーで運用することができます。
一方、astro-notion-blogは一般的なブログでよくある「いいねボタン」や「コメント」にはデフォルトでは対応しておらず、それらの機能が欲しいと思う人もいらっしゃるでしょう。
そこでこの記事では、astro-notion-blogにいいねボタンを実装する方法を説明します。
今回はいいね数、いいねボタンを記事個別ページに設置するとして話を進めていきます。
まずはいいねボタンから考えていきましょう。
いいねボタンは、ボタンが押されるとNotionデータベースのいいね数を 現在のいいね数+1 の値に更新し、ページに表示しているいいね数を最新の値で更新するという仕様です。
いいね数を更新するにはNotion APIをコールする必要があります。しかし、デフォルトのastro-notion-blogにはこのような処理を実行するサーバーがありません。
しかし幸いなことに、astro-notion-blogがベースとしているAstroはオプション1つでSSRを有効にでき、このようなサーバー処理が可能になります。
また、ホスティングサービス側もAstroのSSRに対応しているものがいくつかあります。詳細は下記の公式ドキュメントをご覧ください。
この記事ではVercelの例を説明します。
SSRを用いてNotion APIをリクエストするためのAPIエンドポイントの実装を考えます。
APIエンドポイントは POST /api/likes.json
とし、どの記事に対する「いいね」かがわかるように記事のSlugを付けて POST /api/likes.json?slug=aaa
のようにリクエストすることにしましょう。
レスポンスでは更新後の最新の「いいね数」を返すことにします。そうすることで「いいねボタン」を押した後に「いいね数」の更新がスムーズに行えます。
レスポンスの形式はJSONで下記のようにします。
{"likes": 1}
いいねボタンが押されたとき、クライアントサイドのJavaScriptからこのAPIをリクエストします。
レスポンスを受け取ったらステータスを調べ、ステータス200(成功)だったらレスポンスボディから最新のいいね数を取得し、ページ内のいいね数を書き換えます。
これで「いいねボタン」は実装できそうです。次に「いいね数」の表示を見ていきましょう。
いいねボタンの実装のためにSSRを有効にすることにしましたが、記事個別ページを動的生成にすべきかどうかは検討の余地があります。
すなわち、下記の2パターンを比較、検討する必要があるでしょう。
- 記事個別ページ自体を動的生成にして最新のいいね数を表示する
- 記事個別ページは静的生成のままにして、最新のいいね数の表示はAPIを利用する
ページを動的生成するということは、ページ表示時の速度を犠牲にするということです。
一方、後者はページを静的生成のままに保ちます。APIレスポンスが返ってくるまでは「いいね数」が表示されないことになりますが、ブログというアプリケーションの特性上そこまで気にはならないでしょう。
そういうわけで今回は2の方法で実装していきます。「いいねボタン」のAPIがほぼ流用できるので実装の手間も少なくて済みます。
「いいね数」を取得するAPIエンドポイントは GET /api/likes.json
とし、「いいねボタン」と同様、どの記事かわかるように GET /api/likes.json?slug=aaa
のようにSlugを合わせて受け取ることにします。
レスポンスは「いいねボタン」と同様に、現在のいいね数をJSONで返します。
これで、システムと仕様が決まったので実装に入りましょう。
最初にNotionデータベースにいいね数を表す Like
プロパティを追加します。プロパティの種類は数値(Number)です。
追加した Like
プロパティをastro-notion-blog全体で扱えるように src/lib/interfaces.ts
に差分を追加します。
export interface Post {
PageId: string
Title: string
Icon: FileObject | Emoji | null
Cover: FileObject | null
Slug: string
Date: string
Tags: SelectProperty[]
Excerpt: string
FeaturedImage: FileObject | null
Rank: number
+ Like: number
}
Like
を更新する際のリクエストパラメータの型も定義しておきます。
src/lib/notion/request-params.ts
に下記の差分を追加します。
+ export interface UpdatePage {
+ page_id: string
+ properties: PageProperties
+ }
+
+ interface PageProperties {
+ [key: string]: PageProperty
+ }
+
+ interface PageProperty {
+ number?: number
+ }
src/lib/notion/responses.ts
にレスポンスも定義しておきます。
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
+ export interface UpdatePageResponse extends PageObject {}
src/lib/notion/client.ts
で Like
を Post
に設定するために下記の差分を追加します。
function _buildPost(pageObject: responses.PageObject): Post {
...
FeaturedImage: featuredImage,
Rank: prop.Rank.number ? prop.Rank.number : 0,
+ Like: prop.Like.number ? prop.Like.number : 0,
}
同じく src/lib/notion/client.ts
に Like
を更新するためのメソッドを実装します。
+ export async function incrementLikes(post:Post): Promise<Post|null> {
+ let result: responses.PageObject
+
+ const params: requestParams.UpdatePage = {
+ page_id: post.PageId,
+ properties: {
+ Like: {
+ number: (post.Like || 0) + 1,
+ },
+ },
+ }
+
+ result = (await client.pages.update(
+ params as any // eslint-disable-line @typescript-eslint/no-explicit-any
+ )) as responses.UpdatePageResponse
+
+ if (!result) {
+ return null
+ }
+
+ return _buildPost(result)
+ }
次にSSRを有効にします。
SSRを有効にするにはクラウドサービスに応じたアダプターをインストールし astro.config.mjs
に設定を追加する必要があります。
SSRに対応するためのコマンドが用意されています。下記コマンドを実行して差分をコミットします。
yarn astro add vercel
もしくは、次のように手動で対応することもできます。
@astrojs/vercel
をインストールし package.json
と yarn.lock
をコミットします。
yarn add @astrojs/vercel
astro.config.mjs
に下記の差分を追加します。
+ import vercel from '@astrojs/vercel/serverless';
export default defineConfig({
+ output: 'server',
+ adapter: vercel(),
...
});
SSRはアプリケーション全体に渡って有効になってしまうため、既存のページまで動的生成になってしまいます。
APIエンドポイント以外は静的生成を維持したいので、既存のページ全てに export const prerender = true
を追加します。
例えば記事個別ページは下記のように追加します。
---
...
import styles from '../../styles/blog.module.css'
+ export const prerender = true
+
export async function getStaticPaths() {
次にAPIエンドポイントを実装します。
src/pages/api/likes.json.ts
を次のような内容で作成します。
import type { APIRoute, APIContext } from 'astro'
import { getPostBySlug, incrementLikes } from '../../lib/notion/client'
export const get: APIRoute = async ({ request }: APIContext) => {
const url = new URL(request.url)
const params = new URLSearchParams(url.search)
const slug = params.get('slug')
if (!slug) {
return new Response(null, { status: 400 })
}
const post = await getPostBySlug(slug)
if (!post) {
return new Response(null, { status: 404 })
}
return new Response(JSON.stringify({ likes: post.Like }), {
status: 200,
headers: {
'Content-Type': 'application/json',
},
})
}
export const post: APIRoute = async ({ request }: APIContext) => {
const url = new URL(request.url)
const params = new URLSearchParams(url.search)
const slug = params.get('slug')
if (!slug) {
return new Response(null, { status: 400 })
}
const post = await getPostBySlug(slug)
if (!post) {
return new Response(null, { status: 404 })
}
const updatedPost = await incrementLikes(post)
if (!updatedPost) {
return new Response(null, { status: 404 })
}
return new Response(JSON.stringify({ likes: updatedPost.Like }), {
status: 200,
headers: {
'Content-Type': 'application/json',
},
})
}
これでいいね数といいねボタンの準備が整いました。最後に記事個別ページにいいねボタンを実装します。
いいねボタンは ❤️ 5 のようにいいね数と一緒に表示することにし LikeButton
コンポーネントとして実装します。
src/components/LikeButton.astro
を下記のように実装します。スタイルはお好みで調整してください。
---
export interface Props {
post: Post
}
const { post } = Astro.props
---
<button class="like-button" data-slug={post.Slug}>❤️ 0</button>
<style>
.like-button {
margin-top: 1rem;
}
</style>
<script is:inline>
document.addEventListener('DOMContentLoaded', async () => {
const button = document.querySelector('.like-button')
const slug = button.dataset.slug
const url = `/api/likes.json?slug=${slug}`
const res = await fetch(url)
if (res.status !== 200) {
throw new Error('Failed to like get')
}
const { likes } = await res.json()
button.textContent = `❤️ ${likes}`
button.addEventListener('click', async () => {
const res = await fetch(url, { method: 'POST' })
if (res.status !== 200) {
throw new Error('Failed to like post')
}
const { likes } = await res.json()
button.textContent = `❤️ ${likes}`
})
})
</script>
記事個別ページ src/pages/posts/[slug].astro
に LikeButton
コンポーネントを追加します。
import BlogTagsLink from '../../components/BlogTagsLink.astro'
+ import LikeButton from '../../components/LikeButton.astro'
import styles from '../../styles/blog.module.css'
<PostTags post={post} />
+ <LikeButton post={post} />
<footer>
push する前に yarn dev
で動作確認しておきましょう。
記事のフッターに「いいねボタン」が表示されています。
最終的な全体差分は下記をご覧ください。
以上です。この記事では、astro-notion-blogにいいねボタンを実装する方法を説明しました。
コメントを送る
コメントはブログオーナーのみ閲覧できます