技術・Web access_time update

Firestoreのデータからブログのサイドバーを表示する

 WordPressブログをNuxt+Firebaseに移行するシリーズ。

Image

Firestoreのデータからブログを表示する

 WordPressブログをNuxt+Firebaseに移行するシリーズ。 [nlink id="6077"]  WordPressに存在する記事... 続きを読む

access_time2019-11-17

 前回の記事で、記事一覧および記事本文を表示できるようになりましたが、

 サイドバー、関連記事、コメントフォームなどの共通パーツがまだです。


サイドバー

 このブログの場合、サイドバーに必要な機能は以下の通り。

  • 最新記事
  • 最新コメント
  • カテゴリー一覧
  • タグ一覧
  • アーカイブ
  • カレンダー
  • 人気の記事
  • 広告

 意外と多くてしんどいのですが、そもそもこのあたりの必要データ構造は↓の記事で整えたので、

 この記事ではあんまり書くことがなさそうです。

WordPressのカテゴリー・タグなどのデータをFirestoreに同期する | Our Story's Diary


 サイドバーを共通パーツ化することで、ページ遷移時に再読み込みしなくて良くなります。

 これをいい感じに差分更新してくれるのがNuxt化の最大のメリットであり、WordPressだとajaxとかで無理やり遷移させることになりますが、どこを書き換えてどこを残すかを人間の頭で管理するのは不可能です


サイドバー

最新記事

 これはトップページのメインループと同じ関数なので割愛。limitを5にするくらい。


最新コメント

 これもWordPressからWP REST API経由で取るので割愛。


カテゴリー一覧

 カテゴリー一覧の取得クエリは

  async categories() {
    const query = db.collection('categories').orderBy('name') // タグはtags
    const data = await query.get().then(querySnapshot => {
      return querySnapshot.docs.map(doc => doc.data())
    })
    return data
  }

 実際に出力する際は

      <ul class="menu-list">
        <li v-for="category in categories" :key="category.id">
          <n-link :to="`/category/${category.slug}`">
            {{ category.name }}
          </n-link>
        </li>
      </ul>

 で前回作ったカテゴリーページにリンクできます。


 ちなみにこのブログでは、サイドバーにはアスタリスクから始まるカテゴリーのみを表示するようにしています。(最近使っていないカテゴリーが無駄に多いので)

    const data = await db
      .collection('categories')
      .orderBy('name')
      .startAt('*')
      .endAt('*' + '\uf8ff')
      .get()
      .then(querySnapshot => {
        return querySnapshot.docs.map(doc => doc.data())
      })

 Cloud FirestoreでLike検索する方法|shogo yamada|note の記事を


タグ一覧

 タグは上位20件をcountで並べます。

    const query = db
      .collection('tags')
      .orderBy('count', 'desc')
      .limit(20)
    const data = await query.get().then(querySnapshot => {
      return querySnapshot.docs.map(doc => doc.data())
    })
    return data

 カウントを使っているということはWordPressなしに実現させるのは不可能ということです。その場合はCloud Functionsでインクリメントさせれば良いかもしれませんが。


月別アーカイブ

 アーカイブコレクションに入れたものをyearでグルーピングします。Lodash 使えば良いと思うのでコードは省略。


カレンダー

 これに関してはFirestoreとかじゃなくJSの話なので割愛……別にカレンダー作りたい人いないと思うので。

 days with postというドキュメントに記事が存在する日付を配列で入れておいて、それとは別にカレンダー作ってマージするイメージです。


人気の記事

 ランキングコレクションに入れたものを取得。

    const query = db.collection('ranking').orderBy('session', 'desc')


記事ページ

 記事ページの関連記事、WordPressのプラグインだったりNoteのようなメディアサイトだと、文章を解析してスマートに表示するアルゴリズムがあったりするのでしょうが、

 それをやった結果変に昔の記事がリコメンドされても嫌なので、

 「同じカテゴリーの最新記事5件」と「同じタグを1つ以上含む最新記事5件」を表示させています。


最新記事

 category slug渡すだけなのでカテゴリーアーカイブページと同じです。


同じタグを含む記事

 実はこれを一回のクエリで実現するのは、10月までは不可能でした

 おそらく無理に実行したら、「持っているタグごとにそれぞれの最新5件を取得して、フロント側でマージしてから日付順に並べ替えて最新5件だけを残す」という処理になったと思います。

 しかし、つい最近、「array-contains-any」という新たなクエリがサポートされました。

FirebaseのFirestoreに便利なクエリ「array-contains-any」「where-in」ってのができたからサンプルサイトとともに解説するぞ! – フロントエンドの地獄

 このおかげで全てが解決しました。

  async tagsIncludePosts(slugs: string[], postId: number) {
    const limit: number = 6
    slugs = slugs.map(slug => encodeURI(slug).toLowerCase())
    const query = db
      .collection('posts')
      .where('tag_slugs', 'array-contains-any', slugs)
      .orderBy('date', 'desc')
    const data = await query
      .limit(limit)
      .get()
      .then(querySnapshot => {
        return querySnapshot.docs.map(doc => new Post(doc.data()))
      })
    return data.filter(p => p.id !== postId).filter((_, i) => i < 5)
  }

 tag-slugの配列を渡して6件取得しています。なぜ6件かというと、同じ記事を省くためです。

 IDをexcludeするクエリを足せばもしかしたら不要かもしれませんが、whereを複数書く際の制約などに引っかかりそう(未確認)だったのでフロント側で弾くことにしました。


総括

 ここまでやった時点で、実はもうブログの表示は完成しています。

 いいねとかコメントとかの取得しかできない状態のままですし、SSRまたは静的サイト化しないとTwitterにOGPを表示できないという問題も残ってはいますが、

image-20191124133309292

 こんな感じでWordPressのフロントと遜色ない表示は既にできていますので、技術的には完了です。

 技術的には。


 これで完成したと思って技術検証中に、思わぬ落とし穴が。

 それは、読み取り回数が異常な速度で増えているということ。

 Firestore無料プランの読み取り回数の上限5万回というのは、普通に個人で開発していたらそうそう到達しないのですが、

image-20191124133523336

 一瞬で5万回到達して死んだので、開発続けるために一時的に従量制プランに引き上げました。

 

 で、いろいろ調べたところ、「読み取り回数のカウントはクエリ単位ではなくドキュメント単位」という情報が。

 つまり、

  • メイン記事10件
  • 人気記事10件
  • 最新記事5件
  • アーカイブ約120件(1ヶ月ごとに別ドキュメントに入れたものが10年分あるため)
  • メインカテゴリー約10件
  • タグ20件

 で、ページ表示するごとに180回近くの読み取り処理を行っている計算になります。

 もちろんページ遷移ならサイドバーの更新は行われませんが、2ページ目を見に行ったり別のページに遷移すれば10カウントずつ増えていく。

 ということは、1日に約250人が訪れたら読み取りエラーになるということです。

 いくら弱小ブログとはいえ、250人はたぶん来る。毎日ではないかもしれないけど。というかさすがに250人以下であることを前提には公開できない。


 というわけで次回の記事では、この読み取り回数をどう減らしていくかという戦いについて書きます。

 とりあえずそろそろ解決しそうな目途は立ちつつありますが……。こればかりは実際動かしてみてアクセス数と読み取り回数を見てみないと判断しづらいので、多少時間かかりそうです。

 もしくはコメントといいねをREST APIに投げる処理の記事を先に書くかもしれません。

 どちらにしても次はちょっと先になるような気がしますが、よろしくお願いします。