Kindle のセール本をウォッチできるサイトを作った。


数年前に Kindle を購入してから結構本を読むようになり、今はだいたい年間 80 冊程度読んでいます。半額セールとか 50% ポイント還元とか、そういったセールを Kindle ストアは頻繁に行っているので、そういうときに一気にまとめ買いするのが習慣になりました。

アマゾンのほしい物リストに本を登録しておくと、安くなったときにその旨表示されて便利なんですが、これってポイント還元の時は分からないのでちょっと困っていました。どんなキャンペーンなのかはあまり重要ではなく、欲しい本が得になっているかどうかだけ分かればいいので、そういった事が簡単に分かるサービスはないかな〜と探してみたんですが、これといったものを見つけられず。

…ということで、せっかくなので前から気になっていた技術をいくつか使って勉強しながら習作してみることにしました。試行錯誤しながらコード書いてたら結局 1 ヶ月くらいかかってしまった…

あくまで習作なので、サイト運営やデザインは気にせずにとにかく作ってみることを最優先にしました。Kindle のセール情報サイトはたくさんあって「きん〜」という名称が多いみたいなのでそれを真似して 5 秒くらいで決定。ロゴも自分の Kindle をスマホで撮って文字を入れただけ。デザインも… 💩 。ドメインはこだわりがないので Firebase Hosting そのまんまで。スペルからすると kin-cam かなとも思いますがここはシンプルに kin-can で。対象からアダルトは除外。漫画もキャンペーンが多すぎて追いきれないので自分が気になったもののみ。…という雑な感じではありますが、一応動くものができたので公開しました。

きんきゃん! Kindleキャンペーン・セール本検索サイト
(残念ながらアフィリエイトの審査に通らなかったため公開終了しました)

特徴

前から気になっていた以下の技術を使って作りました。

  • Next.js
  • AMP
  • SSG
  • Firebase (Firestore, Authentication, Functions)

React 好きなので Next.js はもちろん知っていましたが縁がなくて本格的に使ったことはなし。調べてみるとどうやら SSG としても使えるとのこと。さらにサイトを AMP としても出力できると知りまさに一石三鳥。今回はこれを採用しました。最初は Gatsby を検討したので、こちらもそのうち勉強してみたいなと思っています。

バックエンドは前からやりたいと思っていた Firebase を選択。無料枠があり、習作ということでなんとか無料で済ませたいという条件に合っていたため選びました。

こういうサイトがあったらいいなあと自分が思うものを作ったので万人受けするかどうかはかなり怪しいですが、ざっと以下の点を意識しました。

  • 著者や出版社といった軸でドリルダウンできるようにする
  • AMP で構築して早く表示されるようにする
  • 機能は必要最小限にする
  • 会員登録することでウォッチリストを作成できるようにする
  • リストに好きな著者や本を登録しておくとセールになったときに表示されるようにする

作業メモ

作業していくうえで苦労した点や気づいた点などをいくつか書きたいと思います。

Next.js の AMP 対応

Next.js は pages ディレクトリにページコンポーネントを作っていきますが、そのコンポーネントから以下のように config 変数を export するだけです。すごく簡単でびっくりしました。true 以外にも、AMP と非 AMP を混在させる hybrid という値を設定できますが、今回は true のみ使いました。

export const config = {
  amp: true,
};

AMP サイトは外部 CSS を使用できないので、CSS は style タグで HTML に直書きする必要があります。その際は以下のように amp-custom 属性を付けます。

<style amp-custom>...</style>

しかし、自分のコンポーネント設計が問題だったのかどうか分かりませんが、なぜかこの属性値を持つ style タグが複数出てしまい、AMP のバリデーションエラーになってしまいました。そこで以下の記事を参考に pages/_document.tsx を作成することで解決しました。

next.js の AMP mode を使って静的サイトを作る

Next.js の SSG 対応

これもすごくシンプルでわかりやすかったです。以下のように getStaticPropsgetStaticPaths という関数を export するだけ。前者は SSG するときのコンポーネントの props を静的に定義するもの。後者はルーティングしているときに、URL の全パターンを Next.js に教えてあげるために使われます。ルーティングしていないなら後者は不要ですね。

export async function getStaticProps() {
  // ...
}

export async function getStaticPaths() {
  // ...
}

Firebase のローカルエミュレーター

ばりばり開発しているときに本番の Firebase を使用すると料金的にいろいろ大変なので、開発時はエミュレーターを起動してローカルの Firebase サービスを使用します。エミュレーターへの接続方法はいくつかありますが、Firebase Hosting を使用しているなら以下のように script タグにクエリを付けるのがシンプルで一番手っ取り早いと思います。

<script src="/__/firebase/init.js?useEmulator=true"></script>

エミュレーターはかなり高機能で、Firestore, Authentication, Functions すべて問題なく使用できたのですが、1点だけ躓いた点がありました。エミュレーターを起動するときに --export-on-exit フラグを付けると、エミュレーターを終了した時に自動でデータ保存してくれるようになり、データの永続化が可能です。が、なぜかうまく保存されないときがあり、書き込んだはずの Firestore のデータが無くなってしまうことがありました。条件は不明ですが、長時間エミュレーターを起動していてかつ数万件のデータを頻繁に読み書きしたあとによく起きたような気がします。謎。結局、データを JSON にダンプするバッチを書いて、都度インポート・エクスポートするようにして乗り切りました。

Firebase コンソールの操作も課金対象

きちんとドキュメントに書いてあるんですが、ここはちょっと盲点でした。やはり GUI だと分かりやすいので、Firebase コンソールでデータをいじったり確認したりしていたのですが、それらの操作もすべて課金対象になります。さいわい無料枠をはみ出ることはありませんでしたが、Firestore に数千〜数万件のダミーデータを入れて試行錯誤したりしてたので結構危なかったです。しかもフィールド情報を表示していなくても、コレクション内のドキュメントのリストを閲覧しただけで大量の読み取りがカウントされるらしく、かなり焦りました。今どのくらいドキュメントあるのかな〜などと適当にリストをスクロールしてたらいきなり数千カウントされてしまい…。今後気をつけたいと思います。


うおおぉっ!?

Cloud Functions のパフォーマンス

無料枠だとメモリ割り当てが少ないのでかなり遅く感じますね。開発中はユーザーも自分ひとりでコール頻度も低いため、実測 6, 7 秒はかかってる感じ。もう少し様子を見ながら、ユーザー数とパフォーマンスの相関を観察してみたいと思っています。

おわりに

Firebase は面白いですね。コストを抑えるためにはなるべくデータを集中させて読み書き頻度を抑えた方がよさそうなので、そういった設計のキモみたいな部分の知識をもっと増やしたい。Next.js も今回は機能のほんの一部しか使っていないのでもうちょっと複雑なサイトを作って経験値を上げていきたいなと思っています。

最後まで読んでいただきありがとうございました。