🐙

積読が崩せないので読書記録サービスをつくった

2024/10/23に公開

背景と動機

本をよく買います。
気になった技術書や小説、漫画など、社会人になってからは特に買う頻度が増えたように思います。

しかし、読む時間がなかなか取れなかったり、期間が開いてどの本を読んでいたのか忘れてしまったりして、気付いたら積読が積み上がり「あ~やべ…」と感じることが多くなっていました。

「今読んでる本」をシンプルに管理できる何かがあれば、この状況を打破できるのでは…!と思ったので、読書記録サービスをつくってみました。

つくったもの

yondako という読書記録サービスをつくりました。
GitHub か Google アカウントで登録できます。

https://yondako.com

デモ動画はこんな感じです。

https://x.com/_arrow2nd/status/1830184848570646972

「よみたい」「よんでる」「よんだ」の3状態で読書状況を記録できます。

読書記録のダイアログ

レスポンシブ対応もしていて、スマホからも利用できます。

スマホでの表示例

SNS的な要素や感想の記録などをあえて排除して、読書状況の記録に特化しているのが特徴です。

技術スタック

採用した技術・ツールを紹介しつつ、それぞれの選定理由を振り返ってみます。

ランタイム

Node.js ではなく Bun を採用しました。

https://bun.sh/

せっかくなので普段使わないものを使ってみたい!と思ったのが理由です。
パッケージマネージャーの機能や Jest 互換のテストランナーである bun:test も利用しています。

フレームワーク

Next.js を採用しました。

https://nextjs.org/

当初は HonoX を採用して開発を進めていたのですが、Bun + react-renderer + オレオレ証明書な環境で

Clone our library locally and change line 35 in react-renderer.ts to:
const { renderToReadableStream } = await import('react-dom/server.browser')
middleware/packages/react-renderer/src/react-renderer.ts
Line 35 in 61cd9b6
const { renderToReadableStream } = await import('react-dom/server')

のようなエラーが出てしまい詰まってしまいました。

おそらく以下の Issue が近い状況かなと思うのですが、諸事情で早く動く状態まで持っていきたかったので Next.js に切り替えたという経緯があります…。

https://github.com/honojs/middleware/issues/627

もしかすると、今は改善されているかもしれません。

スタイリング

Tailwind CSS を採用しました。

https://tailwindcss.com/

一番使い慣れているというのと、Copilotとの相性が良いと感じているのが理由です。

アニメーション

react-spring を採用しました。

https://www.react-spring.dev/

これも使い慣れているというのが一番の決め手ですが、慣性の効いたリッチな動きを簡単に作ることができるのというのも理由の1つです。

後述するダイアログのアニメーションで活躍してくれました。

認証

Auth.js を採用しました。

https://authjs.dev/

認証周りの知見があまりないのでライブラリにまるっと任せたかったのと、Next.jsを採用しているのでこれかな~という感じで選びました。

データベース

ORM には Drizzle を、データベースには Cloudflare D1 を採用しました。

https://orm.drizzle.team/
https://www.cloudflare.com/ja-jp/developer-platform/d1/

主にコスト面の理由で Cloudflare に全乗りしてみたい!という気持ちがあったのと、この分野の知見があまりにも無く「なんか記事でよく見る組み合わせだな~」と思ったので選びました。

以下の記事がとてもとても参考になりました 🙏

https://zenn.dev/mizchi/articles/d1-drizzle-orm
https://zenn.dev/daichi2mori/articles/20240515-cf-d1-hono-drizzle

特に Drizzle は Prisma Studio ならぬ Drizzle Studio というツールがあり、ブラウザ上でテーブルの中身を確認できるのがとてもありがたかったです。

デプロイ

Cloudflare Pages を利用しました。

https://www.cloudflare.com/ja-jp/developer-platform/pages/

Next.js を使っているので Vercel にデプロイするのが無難なところですが、前述したように Cloudflare に全乗りしたかったのと、やろうと思えば広告等で収益化もできそうだったのが決め手になりました。

https://community.cloudflare.com/t/is-cloudflare-pages-workers-free-plan-free-for-commercial-use/291741/3

ただ、ISR / オンデマンドISR が利用できないのが少しつらいな…と感じています。

Next.js does not support building ISR pages for the edge runtime, and as such, pages should be changed to use server side rendering (SSR) instead.
https://github.com/cloudflare/next-on-pages/blob/main/packages/next-on-pages/docs/supported.md#rendering-and-data-fetching

書籍情報の取得

国立国会図書館サーチAPIを利用しています。

https://ndlsearch.ndl.go.jp

詳細に書くと、書籍情報は検索用API(OpenSearch)を、書影には書影APIを利用しました。
利用してみていくつか得られた知見があるので、それらは別途記事にできたらなと思っています。

(追記:書きました!)
https://zenn.dev/chot/articles/24ea6186c029b0

また、広告などで収益化する場合は利用申請が必要とのことでした。
利用申請を送ると API のメンテナンスやデータの更新時に通知してくださるので、商用利用の有無に関わらず申請しておくとよさそうです!

その他ツールチェーン

  • biome
  • Storybook
    • コンポーネントのユニットテストや、Playwrightと組み合わせてVRTをするためにも利用しています
  • mise
    • Bunのバージョン管理に利用しています。GitHub Actions 上でも動くので開発環境とCI環境のバージョンを一元管理でき便利です

デザイン

Figma を利用しました。

https://figma.com/

絶賛 Figma 使いこなせるようになりたい週間実施中なので触ってみたかったのが理由です。

Figmaの画面キャプチャ

途中、締切駆動での開発に移行したため、Figma でデザインを起こさずに実装した部分が多くなったことが反省点です…。

ここ頑張ったかも!

ダイアログのアニメーション

表示時はクリックしたカードの位置を起点にダイアログが広がっていくような動き、閉じる時は煙のようにふわっと消えていきます。

ダイアログのアニメーション

react-spring は mass, tension, friction などの物理的なパラメータを指定して動きを作ることができるので、自然な動きを作るのにとても便利です。

この例では mass: 1.1 とすることで、開く時の微妙なバウンドを実装しています。

const transitions = useTransition(isOpen, {
  from: () => {
    const rect = ref.current?.getBoundingClientRect();

    // 書籍カードの中央座標を%で算出
    const top = rect?.top
      ? `${((rect.top + rect.height * 0.5) / window.innerHeight) * 100}%`
      : "50%";

    const left = rect?.left
      ? `${((rect.left + rect.width * 0.5) / window.innerWidth) * 100}%`
      : "50%";

    return {
      scale: 0.25,
      opacity: 0,
      filter: "blur(8px)",
      top,
      left
    };
  },
  enter: {
    scale: 1,
    opacity: 1,
    filter: "blur(0px)",
    top: "50%",
    left: "50%",
    config: {
      mass: 1.1, // 少しバウンドさせる
      tension: 250,
      friction: 28
    }
  },
  leave: {
    scale: 1.025,
    opacity: 0,
    filter: "blur(16px)",
    config: {
      mass: 0.8,
      tension: 200,
      friction: 26
    }
  }
});

指定できるパラメータは他にもたくさんあるので、詳しくは公式ドキュメントを参照してください。

https://react-spring.dev/docs/advanced/config#reference

バーコードで検索

画面いっぱいにカメラ映像を表示したかったので、「スキャン領域を指定できる」という要件が必須でした。

その要件を満たすものとして、QuaggaJS というライブラリが候補に上がったのですがメンテナンスされていないようだったので、そのフォークである Quagga2 を利用しました。

メンテナの方が React でのサンプルを公開してくださっていたので、それを参考に実装しています。
https://github.com/ericblade/quagga2-react-example

これで完璧!と思っていたのですが、実機で動作確認するとうまく動かない機種があったりしてとても苦労しました…というより、現在も苦労しています 😿

バーコードで検索のエラーページのスクリーンショット
最近変えたスマホでデモを撮ろうとしたら動かなかった図

主要機能の開発を優先していたためまだまだ不安定ですが、その分逆に改善しがいがありそうです。

今後の課題として取り組んでいきたいと思います!

積読は崩せた?

積読数のスクリーンショット
開発開始時は35冊とかだった気がします

崩せています!
が、「積読は崩せる」ということを知ってしまったので、逆に積読が増える結果となりました。

とはいえ、当初の目的は達成できているといえる…はず…。

おわりに

個人開発でWebサービスをつくったことがなかったので、色んな知見を得ることができとても楽しかったです。

また、いかに自分が期限を設けないと開発が進まないかを痛感しました…。
細かいところが気になって手戻りしてしまうことが多かったので、熱が冷めないうちにある程度大雑把にガッとつくることも大事だなと思いました。

自分が使いたくてつくったサービスなので、積読を崩し終えるその日まで使い続けていきたいです~!


リポジトリはこちらです 🐙
https://github.com/yondako/yondako

Discussion