Site cover image
Site icon

すいむろぐ

日記を中心にいろいろと。

💞 astro-notion-blogの改修をして、ハートがいい感じに送れるようにした

ハートを送れるボタンをつけました

本ブログに、記事ごとのハートボタンを追加しました👏

Image in a image block

一番下の方につけており、押すとハート数が増えるようになっています💞

右側の数字で、いままで何人がハートボタンをクリックしたかが確認できます!

参考にした記事

今回の実装では、おとよさんの 「astro-notion-blog にいいねボタンを追加する」記事を参考にしました。

この記事では、Notion の Like プロパティを使ったいいねボタンの実装方法が紹介されています。

incrementLikes 関数で Like を +1 して Notion に書き戻すというシンプルな構成です。

// 参考記事の incrementLikes(簡略版)
export async function incrementLikes(post: Post): Promise<Post | null> {
  const params: requestParams.UpdatePage = {
    page_id: post.PageId,
    properties: {
      Like: {
        number: (post.Like || 0) + 1,
      },
    },
  }

  const result = (await client.pages.update(
    params as any
  )) as responses.UpdatePageResponse

  if (!result) {
    return null
  }

  return _buildPost(result)
}

この実装をベースに動かしてみたところ、ひとつ問題が見つかりました。

問題: キャッシュによるLike値のズレ

astro-notion-blog では、getAllPosts()の結果をメモリキャッシュ(postsCache)に保持しています。
getPostBySlug() もこのキャッシュを経由するため、API で取得した `post.Like` はビルド時やキャッシュ生成時の値になっていることがあります。

たとえば、こんなケースです。

  1. ビルド時に Like = 5 でキャッシュされる
  2. 誰かがハートを押して Notion 上では Like = 8 になる
  3. API の GET で post.Like を返すと、キャッシュ上の5が返る
  4. POST でpost.Like + 1 = 6を書き込み、Notion 上の86に巻き戻る


ハートを押したのに数が減る、という状態になりかねません。


解決策: キャッシュをバイパスして最新値を取得する

この問題を避けるために、いいね関連の処理ではキャッシュを使わず、

Notion API を直接呼び出して最新値を取得するようにしました。

最新のハート数を取得する


pages.retrieve で対象ページを直接取得し、Like プロパティの値を返しています。

export async function getLatestLikes(post: Post): Promise<number> {
  const page = (await client.pages.retrieve({
    page_id: post.PageId,
  })) as responses.PageObject

  const latestPost = _buildPost(page)
  return latestPost.Like || 0
}
ハートを1件増やす

incrementLikes 自体は参考記事と同じシンプルな形にしています。渡された post.Like を +1 して更新するだけです。

export async function incrementLikes(post: Post): Promise<Post | null> {
  const params: requestParams.UpdatePage = {
    page_id: post.PageId,
    properties: {
      Like: {
        number: (post.Like || 0) + 1,
      },
    },
  }

  const result = (await client.pages.update(
    params as any
  )) as responses.UpdatePageResponse

  if (!result) {
    return null
  }

  return _buildPost(result)
}
API側で最新値を直接取得する

キャッシュの問題は API ルート側で対処しています。POST 時に getLatestLikes で最新値を取得してから incrementLikes に渡すことで、古い値での上書きを防いでいます。

// likes.json.ts(POST部分)
const post = await getPostBySlug(slug)

if (!post) {
  return new Response(null, { status: 404 })
}

// キャッシュをバイパスして最新のLike値を取得してからインクリメント
const latestLikes = await getLatestLikes(post)
const updatedPost = await incrementLikes({ ...post, Like: latestLikes })

GET 側も同様に getLatestLikes を使い、常に Notion から最新値を返すようにしています。

この実装のよかった点

Astroの静的なよさを崩さず、ハート押したときの動作が不安定にならない

記事本体は静的配信のままにして、ハート数だけ動的に扱えるので、シンプルに実装できます。

ボタンを押した後に数が増えない・減るといった不具合も回避できました。

気をつける点

この実装は、同時に複数人が押した場合に取りこぼす可能性があります。

たとえば同じタイミングで2人が押すと、

  • どちらも Like = 10 を取得
  • どちらも 11 を書き込む

となって、本来 12 になるはずが 11 のままになることが考えられます。

個人ブログではそこまで問題にならないと思ってこの形にしましたが、厳密にカウントしたい場合はNotionでLikeを管理するのではなく、更新競合を扱いやすいDBを使った方がいいかもしれません。

まとめ

参考記事のシンプルな実装をベースに、キャッシュによるLike値のズレという問題に対処しました。

  • incrementLikes は参考記事と同じシンプルな形を維持
  • API 側で getLatestLikes を使い、常に Notion から最新値を取得してからインクリメント
  • 静的な本文表示は Astro に任せ、動的なハート数は Notion API で取得・更新

参考記事がなければここまでスムーズに実装できなかったと思います。ありがとうございました。

↓ この機会にぜひ、♡をポチッとお願いします!