Nuxt.js&Firebase で WordPressブログをフルリニューアルしたまとめ
技術・Web access_time update

Nuxt.js&Firebase で WordPressブログをフルリニューアルしたまとめ

 2020年2月に、WordPressブログ(このサイト)をFirebaseでフルリニューアルしました。

 現在このサイトはFirestoreのデータを取得してFirebase Hostingから配信されています。

 今年2月よりFirebaseでの運用を開始し、3ヶ月ほどかけて少しずつ調整を加えつつ、ようやく費用感などの実態を掴むことができたので、記事としてまとめることにしました。

※FirebaseやNuxt.jsのことをある程度わかっている方に向けて書いていますのでご了承ください。


 サイト更新からデプロイ、表示までの全体像はこのようになっています。

 (※細かい処理の流れ・役割は後で詳しく説明します)

スライド4

 見ての通りFirebaseフル活用です。ここまで全部使っているサイトは珍しいのではと思います。


 よく、「WP REST API + Nuxt.jsでリニューアルしました」とか、「WordPressを捨ててContentful + Netlifyに移行しました」という記事はそれなりに目にしますが、そのどちらでもなく、

 「WordPress上にある記事データが、常にFirestoreに同期され、そのデータを元にNuxt.jsで生成された静的サイトがFirebase Hostingから配信される」という形を取っています。


 一見すると無駄の多い構成に見えますが、これによって

  • Firebaseの豊富な機能と、最適化されたクラウドサーバー
  • Vue.js / Nuxt.jsによるUX向上と、PWA・静的化などの最新技術への対応しやすさ
  • WordPressの優秀な管理画面・データベース・プラグイン

 というそれぞれのいいとこどりができています。


 実運用を開始して2ヶ月半ほど経過しましたが、特に大きな問題もなく、多くの恩恵を受けられています。

 典型的なWordPressブログ(約3500ページ)を、URLや機能・使用感などを一切変えることなくFirestoreに丸ごと移行して静的化運用するというのは、あんまり前例がないと思うので、参考になれば良いなと思います。


技術選定の理由

WordPressからFirebaseへの移行

 このブログは2016年から今年1月までWordPress(ロリポップ!レンタルサーバー)で運用していました。そこからの脱却を考え始めたのが1年前。当時考えていた理由は下記の4つでした。

サイトの速度が明らかに遅いため、Lighthouseなどのスコアが上がらないのがSEO・利便性の両面で不利。jQueryやPHP・サーバー応答速度といったボトルネックは、WordPressを使っている限り解消することができない

②NetlifyやFirebaseなどの無料で使えるサービスが充実してきている中、レンタルサーバーを使っているのはコスパも悪い

③今後のことを考えると、記事データはMySQLではなくJSON形式の方がバックアップしやすく安全

Vue.js、CSSフレームワーク、PWA・AMPなどといった新しい技術とWordPressの相性が致命的に悪すぎる

WordPressからFirebase+Nuxt.jsのHeadless CMSに完全移行するまでの記録 ①導入・技術選定 | Our Story's Diary

 最終的には、記事データはWP依存のままだし費用も下がっていないので、当初の想定とは若干ズレていますが、

 とにかくメインの理由は、速度改善のボトルネックになっていたことと、Vue.jsを使いたい(=PHPを使いたくない)という2点。

 以前は会社でよくWordPressを使っていたのですが、昨年6月に転職し、ほとんどNuxt.jsしか書かなくなったため、スキルアップとしても脱却したいという気持ちがますます強くなりました。


なぜWordPressを使い続けるのか

 サーバーレス・静的サイト化・Firestoreに魅了されると「WordPressは要らない!オワコン!」という短絡的な結論に辿り着きやすいです。私も最初は「WordPressをどうやって捨てるか」という方向で考えていました。

 しかし、WordPressからの移行先として考えると、NoSQLである現状のFirestoreには足りない機能も多いです。

 連番機能、全文検索、カテゴリー・タグ・月別アーカイブの件数カウント。この記事で使っている目次やなどもWordPressプラグインで実現されていますし、管理画面もCMSとしては貧弱。私が数年前から愛用しているOpen Live Writerのようなエディターソフトとの連携も厳しいです。

 もちろん、個々の機能を一つずつ見ていけばCloud FunctionsやFirestoreクエリハック、Algoriaなどの外部サービス連携を使って実装する方法はあるのでしょうが、

 それらを全部やってくれているWordPressというシステムが既にあって動いているのに、わざわざ自分で作り直す理由はどこにあるのでしょうか?

 考えれば考えるほど単なる車輪の再発明にしかなっていないことに気づき、WordPressでデータが更新されるたびにFirestoreにPUTするだけで十分だと考えるようになりました。

 もちろん、将来的にはWordPressへの依存度を段階的に下げていくのが理想です。特に、画像・コメント・検索といった、フロントからWordPressにアクセスしている部分は、セキュリティ観点からもなくしていきたいですし、最終的に管理画面まで不要になれば

 ただ、セキュリティと表示速度というデメリットを回避した上であれば、¥270/月で使える高機能エディター&データ管理ツール&画像ストレージとして、WordPressは十分に有用であると思います。


Nuxt.jsを使う理由

 自分がVue.js / Nuxt.jsをずっと使っていて好きだから、ということに尽きるのですが。

 Nuxtは静的サイト化の機能が組み込まれているためFirebase Hostingとの相性が良いこと、Google Adsense、Google Analytics、PWA Moduleなどの豊富なプラグインが公式に提供されていることなどから、WordPressの機能を維持したまま移行する際にはベストな選択肢なのではないかと思います。

 また、この記事の主題ではありませんが、Vueのv-forv-ifでテンプレートに直接ロジックを書いていく感覚はPHPに近いところがあるので、コードを移植しやすいというメリットがあります。

 例えば、WordPressのトップページで記事一覧とサムネイルを表示する場合、

<?php
if (have_posts()) : while (have_posts()) : the_post();
?>
  <article id="post-<?php the_ID(); ?>" <?php post_class('box is-paddingless'); ?>>
    <?php if (has_post_thumbnail()) : ?>
      <a href="<?php the_permalink(); ?>" class="card-image">
        <img src="<?php the_post_thumbnail_url('full'); ?>">
      </a>
    <?php endif; ?>
    <div class="card-content is-block">
      <!-- ARTICLE CONTENTS --->
    </div>
  </article>
<?php endwhile; ?>
<?php endif; ?>

 一方、Vue.jsでは

<template v-if="posts.length > 0">
  <article
    v-for="(post, index) in posts"
    :id="'post-' + post.id"
    class="box is-paddingless"
  >
    <a
      v-if="post.thumbnail"
      :href="url"
      class="card-image"
    >
      <img :src="post.thumbnail.full"/>
    </a>
    <div class="card-content is-block">
      <!-- ARTICLE CONTENTS --->
    </div>
  </article>
</template>

 このように移植のコストがかなり軽いです。そもそもVue.jsという言語がWordPress・PHP経験者に向いているのではないでしょうか。


なぜFirestore(Firebase)を使うのか

 WordPressのデータを元に静的サイト化するならFirestoreに同期せずに直接NuxtからWordPressのデータを見に行けば良いのでは、と思われる方もいるかもしれませんが、

 まず、3500記事あるこのサイトの全ページを事前生成するためにWP REST APIを叩くとサーバーが死にます。

【コードある】Nuxt + WordPress で netlify 運用が爆速すぎて神だった – Qiita

 上の記事ではintervalで回避していますが、今の記事数で処理ごとにインターバル入れたら今度はCircleCIの無料枠の上限を間違いなく超過します。そもそも今でも20分近くかかっているので。

 さらに、そもそもNuxt.jsの静的化は厳密には完全な静的化ではないので、ページ遷移時にはSPAと同じようにクライアントサイドからデータを取得しに行きます。つまり、WP REST APIを使うとページ遷移時には結局WordPressの速度に縛られてしまい、高速化の恩恵を受けられません。

 以上のことから、Firestoreのデータベースを利用するのが一番良いという結論に至りました。3500というページ数を一気に生成してもFirestoreがエラーを返すことはありません。(1)ただし、1日のドキュメントの読み取り回数の無料枠上限が50000回なので、記事数が30000とかある人は別の方法を考えた方が良いかもしれません



サイト更新の流れ

 最初に貼った図を元に流れを説明します。

スライド4


記事データ更新

 図の緑枠の部分。この処理の流れは以前の記事で詳しく説明しているので大まかなところだけ。

Image

WordPressの記事データをFirestoreに同期する

 WordPressブログをNuxt+Firebaseに移行するシリーズ。概要は↓[nlink id=6056]  今回は最初のステッ... 続きを読む

access_time2019-11-08

 WordPressに記事を投稿すると、WordPressのpublish_post にフックされたFirestore同期のスクリプトが実行されます。

add_action('publish_post', 'save_published_post_to_firestore', 1, 6);

 更新された記事データ、その前後記事、その記事が含まれている月・カテゴリー・タグの件数といった、変更されたデータをcURL経由でCloud Functionsに送信し、

 Cloud Functionsで日付のタイムスタンプ変換などを行いながらFirestoreに保存します。


サイト更新

 図の青枠の部分。

 上記のpublish_postフックで、記事保存と同時にCircleCIにもAPI経由でジョブをトリガーします。

API を使用したジョブのトリガー – CircleCI

Circle CIからFirebase Hostingに自動ビルド&デプロイする – Qiita

 CircleCI上でnuxt generateを行います。Firestoreの記事データ更新は数秒で終わるので、CircleCIが起動して実際にFirestoreからデータを取得するまでにはデータ更新が完了しています。

 この順番を厳密に保証するのであれば、Firestoreのドキュメント更新を検知してトリガーする方法もありますが、無料プランではCloud Functionsから外部サービスにアクセスできない点には注意が必要です。

 ちなみにこのデプロイには20分くらいかかります。更新からサイト反映まで時間がかかる点が静的化の唯一のデメリットと言えますが、ここは割り切るしかないかなと思います。


プッシュ通知

 つい最近実装したプッシュ通知部分。

 Cloud Functions、Cloud Messaging、Realtime Databaseを使っています。

 ドキュメントや記事を参考にいろいろ試しましたが、3日ほどで実装できました。

JavaScript Firebase Cloud Messaging クライアント アプリを設定する

Web Push通知の受信を、Firebase Cloud Messaging(FCM)+Service Worker +Notificationで受信側でコントロールしてみた

 たぶんこれ単体で記事にするべき内容なのですが、長くなるのでさわりだけ。需要があればいつか。

①Cloud MessagingのrequestPermissionで通知の受信許可をリクエスト

②プッシュ通知トークンをRealtime Databaseに送信

③Firestoreのdocument.onCreateトリガーで、追加された記事のタイトル・URL・本文をメッセージに入れ、Realtime Databaseに保存されているトークン一覧に向けてプッシュ通知を送信

firebase-messaging-sw.jsで受信したプッシュ通知をデバイスに表示


サイト表示の役割分担

 表示部分だけの技術を抜き出すと以下のようになります。

スライド2

 Firebase Hostingは単に静的生成したサイトデータをそのままデプロイしているだけなので特に書くことはありません。Nuxt.jsの基本的な実装しかしていないと思います。

Image

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

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

access_time2019-11-17

 ルーティングやデータ取得クエリなどは以前書いたこちらの記事を参照してください。


Cloud Firestore

 メイン。表示している記事データの実体は全てFirestoreから取得している他に、カテゴリーやタグ一覧なども全て持っています。他の3つが例外で、基本的には全てのデータがFirestoreにあると考えて大丈夫です。

image-20200506000757592

 ただし、ここに保存されているデータは全てWordPressのコピーです。「postsに記事が投稿されたらcategoriesとtagsのカウントを1増やして前後リンクを更新する」みたいなことは全部WordPressがやってくれます。

 Firestoreを通す利点は負荷軽減、速度向上、JavaScript経由でのデータ取得のしやすさ(WP REST APIと比較して)。CDNのようなものと思った方が良いかもしれません。


Realtime Database / Firebase Authentication

 補助的な役割。各記事のいいね数はRealtime Databaseに保存しています。

image-20200506001312690

 そもそもなぜこのいいね数がpostsの中に入っていないのかというと、WordPressとの同期が取れないという問題もあるのですが、一番は静的サイト化が行われる記事更新のタイミングでしかいいね数が更新されなくなってしまうため。

 そこで、フロント側でRealtime Databaseにアクセスし、各記事のいいねだけを取得して、フロント側でマージしています(2)最初の数秒だけいいね: 0と表示されてしまう問題はありますが、大きな問題ではないので割り切っています

 Firestoreの別コレクションに保存すれば良いのではないかと思われるかもしれませんが、Firestoreは「取得したドキュメント件数に応じた従量課金である」という制約があり、各記事ごとに1件ずつ細かいデータを取得する、という使い方をすると一瞬でコストが跳ね上がります。

 そこで、データ通信量に応じた従量課金であるRealtime Databaseを併用して、頻繁に更新されるデータはRealtime Databaseに保存することでコストを抑えています。


 Firebase Authenicationでは匿名ユーザーを自動生成することで、いいねしたかどうかの管理に使っています。Authenicationから与えられるユーザーのユニークIDを、Realtime Databaseのキーにすることで、そのユーザーがいいねしたかどうかの制御に使うことができます。

Firebaseで「いいねボタン」を実装してみよう! – Qiita

 いいねボタン自体の仕組みは上記の記事を参考にしました。

 その他、プッシュ通知をオンにしたユーザーの通知トークンとプッシュ通知許可設定(Boolean)もRealtime Databaseに入れています。

 ユーザーからの書き込み権限をRealtime Databaseにしか与えないことで、セキュリティルールが管理しやすくなる、という利点もあります。


Firebase Functions × Firebase Hosting

 いいね数と同様に、記事更新のタイミングと独立して更新したいのがサイドバーに表示する人気記事。

Cloud Functions を使用した動的コンテンツの配信とマイクロサービスのホスティング | Firebase

 Firebase Functionsとexpressを利用してAPIサーバーを作り、Hostingを通して配信しています。

 その結果を元に記事データをFirestoreから取得して、JSON形式で返しています。

https://oswdiary.net/api/popular_posts

 フロントからFirestoreに取得しに行っていないのは、データは1日1回しか変わらないのでサーバー側にキャッシュして高速化しつつドキュメント読み取り回数を減らすためです。(3)なのでその日初めてアクセスしたユーザーは表示まで少し時間かかると思います

 ランキングはWordPressプラグインではなくGoogle Analyticsのアクセス数から出しているので、Firestore移行前後で計測の仕組みは変えずに済みました。


 加えて、Amazonアソシエイトリンクのためのデータを取得するPA API V5へのアクセスも、静的化のタイミングで一気に行うとリクエスト数の上限を超過するため、Cloud Functions経由で記事表示時に行っています。

[amazon_link asins='B07J6FP6NQ' template='Original']

 ↑こういうのです。

 ランキング同様、Hostingでキャッシュ設定しているので、1つの記事にアクセスが殺到してもPA APIに同じリクエストが何度も行くことはありません。

※ちなみにCloud Functionsから外部サービスへの通信はBlazeプランにする必要があります。費用は後で書きますが、どうしても従量制が気になる方は他のサービスを検討してください。


WordPress(ロリポップ!レンタルサーバー)

 フロント側の表示にもWordPressを利用している部分がいくつかあります。

 最大のメリットは全文検索。WP REST APIのクエリに?search=***を指定するとそのキーワードを含む記事で絞り込むことができるため、検索の場合のみFirestoreではなくWP REST APIからデータを取得する設定になっています。当然ながら速度はFirestoreと比べて落ちますが、検索機能を利用するユーザーの少なさを考えれば許容範囲。

 他にはコメント機能も、現時点ではFirebase化せずにWP REST APIのGET/POSTに一任しています。

 もう1つの画像。画像URLはサブドメインに置き換えてそのままレンタルサーバーを見に行っています。Cloud Storageはコストと手間が読めないので今のところ利用していません。

WordPressサイトで画像をWebP(ウェッピー)対応し軽量化する方法 | Tekito style.me

 WordPress側でWebPリダイレクトも行っていること、LazyLoad対応していることなどから、速度的にそこまで大きなデメリットにはなっていないかなと思っています。


 最終的にはここのフロント依存はゼロを目指したいですが、逆に言えばこういう段階的な移行ができるのもWordPressのメリットです。全文検索・コメント・画像の完全置き換えまで目指すとなかなかハードルが上がってしまうので。

 「どこまでFirebaseに移行させても無料枠の範囲を超えないか」という部分を実際に確認しながらハンドリングすることができるのもありがたいです。


ポイント

Firestoreのドキュメント読み取り数を徹底的に削減する

 前述のRealtime Databaseの役割のところでも触れましたが、Firestoreには「1日5万回までの読み取りは無料」という制限があります。

 このルールは通常の個人サイトで超えることはあまりないのですが、この読み取り数のカウントはドキュメント単位です。私は最初「クエリ1回ごとに1カウント」だと勘違いしていました。

 つまり、記事10件を表示すると読み取り数を10回消費しますし、このサイトのようにサイドバーにデータを表示すると、

 メイン記事10件、人気記事10件、最新記事5件、アーカイブ約120件(10年分)、カテゴリー約10件、タグ上位20

 で、ページ表示するごとに180回近くの読み取り処理を行っている計算になりますし、Nuxt generateのタイミングで3500ページ生成すると一瞬で上限に達します。

Nuxt.jsのgenerateプロパティに動的なルーティングを追加する – 独学プログラマ

 対処法としてPayloadを使い、ルーティング時に取得したデータを使う方法がありますが、ルーティング自体の記事データだけではなく、サイドバーに表示するデータもPayloadから生成します。

 例えば最新記事であれば、

    const data = await query
      .limit(limit)
      .get()
      .then(querySnapshot => {
        return querySnapshot.docs.map(doc => new Post(doc.data()))
      })

 でFirestoreから取得できますが、Nuxt generateの際には既に全記事が存在しているので、

  posts(posts: any[], limit: number) {
    return posts.filter((p, index) => limit > index)
  }

 のように、Firestoreのクエリと同じことをJavaScriptで絞り込んでいます。

 この方法により、nuxt generate のタイミングでは最初にルーティングのためのデータ取得以外でFirestoreにアクセスしないので、3500回しか読み取り数を消費しません。1日10回くらいは静的生成を繰り返しても大丈夫です。


 サイドバーも含めて全て静的化されているので、初回アクセス時のドキュメント読み取りはゼロ。

 さらに、記事カードやサイドバーの時点でドキュメントデータを丸ごと取得しているので、サイドバーをクリックした時にはそのデータを使う(新たにFirestoreを見に行かない)という方法でさらにドキュメント読み取りを抑えています。

 カテゴリー一覧やタグ一覧をクリックした時などは10件新たに消費されますが、これを完全にゼロにするならFirestoreを使う意味も特にないのである程度は許容しています。

image-20200506175735495

 このように、サイト更新を行ったタイミングで4000回読み取りが発生しますが、それ以外の時間帯では誤差程度しか消費されておらず、5万回を超えそうになったことは一度もありません。


記事本文のリンクの置換

 WordPressから取得した本文はv-html でフォーマットして表示しています(ユーザー投稿のコンテンツではないのでセキュリティリスクは考慮していません)。

 しかしこれだと記事本文に別の記事へのリンクがあっても、vue-routerによるページ遷移が行われません。(v-htmlnuxt-linkcomponentをコンパイルしません)

 そこでこのサイトでは、正規表現でaタグのクリックイベントを上書きしてからv-htmlで展開しています。

  computed: {
    contentRendered() {
	  return this.content.replace(
          /<a/g,
          `<a onclick="document.getElementById('post-body').dispatchEvent(new CustomEvent('clickLink', {detail: this.href})); return false;"`
        )
    }
  }

 全てのaタグに対して、#post-body要素に紐づけられたclickLinkイベントを、this.hrefを引数として渡して発火させ、通常のaタグリンク時の動作をreturn falseで防ぐためのonclickイベントを追加します。

<div id="post-body" @clickLink="clickLink" v-html="contentRendered"></div>

 テンプレート側はこうなっています。

 clickLinkイベントの中身は、

    clickLink(evt) {
      if (evt.detail.match(/(gif|svg|png|jpg)$/)) {
        this.isModalVisible = true
        this.isModalImage = evt.detail
      } else if (evt.detail.startsWith('#')) {
        SweetScroll.to(evt.detail)
      } else if (evt.detail.startsWith(process.env.appUrl)) {
        this.$router.push(
          evt.detail.replace(process.env.appUrl, '').replace(/\/$/, '')
        )
      } else {
        window.open(evt.detail, '_blank')
      }
      return false
    },

 画像だったらモーダル表示、ページ内リンクならSweetScroll、サイト内リンクなら this.$router.push、それ以外のURLなら別タブ表示、というように分岐させています。

 他にも正規表現でいろいろやっています。imgタグへのlazyloadクラス追加とsrc属性のdata-src変更、scriptタグの除去など。負荷などを考えるとそもそもFirestore保存時に全部処理するのが正しいのでしょうが、Firestoreにはデータのマイグレーションみたいな機能が用意されていないこともあり、このくらいのデータの微調整はフロントでやってしまって良いのではと思います。


WordPressプラグインのCSS

 WordPressプラグインによって機能追加した場合、本文データにはそのHTMLタグなどが含まれた状態でFirestoreに同期されます(記事トップの目次や注釈など)。

 しかし、デプロイ先は全くの別サイトなので、そのCSSやJSは当然ながら同期されません。細かい部分はプラグインごとに自分で実装する必要があります。ここのCSSはWordPressからコピーしても良いし自分でカスタマイズしても良いです。

<style lang="scss" scoped>
/deep/ {
  @import '~assets/style/post_plugin';
}
</style>

 v-htmlで展開したタグにはscope属性が付かないので、deepセレクタでそれ用のCSSを追加しています。目次などの場合はJavaScriptもカスタマイズが必要になります。


 他にも、Twitterの埋め込みをしている場合はscriptタグが実行されないので、widget.jsを別に読み込んで実行しています。

    if (typeof twttr !== 'undefined') {
      twttr.widgets.load()
      return
    }
    const twitterScript = document.createElement('script')
    twitterScript.setAttribute('src', 'https://platform.twitter.com/widgets.js')
    document.head.appendChild(twitterScript)
    twitterScript.onload = () => {
      if (twttr) twttr.widgets.load()
    }

 こんな感じの処理をmountedに書いています。


プッシュ通知とデプロイのタイムラグ

 プッシュ通知はドキュメント作成時に送信されますが、実際にそのページが生成されるのは、CircleCIのデプロイが完了した後です。つまり、通知を受け取ってすぐにアクセスしても、そのページはまだ存在しない、ということがあり得ます。

 対処法として、そのページにpost_idのクエリを渡すと$router.pushで移動するリダイレクト専用のページを一つ作り、プッシュ通知のURLを記事ページではなくそのリダイレクト用ページに設定しています。

 デプロイを待たなければならないのは静的化されているページにアクセスする場合のみで、ページ遷移であれば記事データはFirestoreに取得しに行くので、デプロイ前でも記事ページを表示できる、ということを利用しています。


運用してみての結果

 ここまで説明してきたやり方での運用を2月中旬に開始して、そろそろ3ヶ月が経ったので、どのようなメリット・デメリットがあったかを書いていきます。


メリット1:表示速度が劇的に速くなった

 とにかく恩恵はこれに尽きます。

img

image-20200212222001459

 リニューアル直後の数字がこちら。アクセスするたびに変わるのであんまり良い比較でもないのですが……。

 WordPressというかロリポップも速度改善アップデートを入れていたりするので、初回アクセスの速度はそこまで大きく変わらないのかもしれません。

 どちらかというと大きく変わったのはNuxt.jsによるページ遷移速度の大幅改善。サイドバーや記事一覧からのページ遷移は、上記の通り新たにデータを取りに行くわけではないので本当に一瞬ですし、記事内URLからの遷移であっても、サイドバーなどを読み込み直さないのでかなり速くなっています。

 他にも、PWAのオフラインキャッシュやCDNの恩恵もあり、全体的な改善は大きいと思います。


 より実際のデータを見るために、Google Analyticsで2019年の平均読み込み時間と、2020年4月の平均読み込み時間を比較してみました。

image-20200506200539555

 このように、平均読み込み時間でどちらも5~6割ほど改善されたことがわかります。……なぜ新規訪問者の方が速いのかはよくわかりませんが。

 ページ別に見ると、写真やAmazonリンクの多い記事ほど速度改善率が高い傾向にありました(WordPressの時に遅くなりやすかったとも言えます)。


メリット2:Firebase、Nuxt.jsの様々な機能を活用できるようになった

 ユーザー管理、プッシュ通知などといったFirebaseの豊富な機能を簡単に導入できるようになりましたし、Nuxt.jsのモジュール・プラグインを使った機能追加も手軽にできるようになりました。

 フロントをWordPressで作っていた時と比較して、開発しやすさが段違いに向上しました。


メリット3:URLや編集環境をほとんど変えずに移行できた

 このサイトは技術検証のためだけのサイトではなく、普通に自分で日記を書くためのサイトでもあるので、Live Writerなどを使った更新の手順を変えずに済む、ということ自体が大きなメリットでした。

 また、URLのルーティングも変わらないように気を配ったので、AnalyticsのデータやSEO評価なども連続性を失わずに移行できた(と思います)。 


デメリット1:記事編集はちょっと不便になった

 WordPressから同期してその都度静的化するという仕様上、やっぱり修正はちょっと面倒です。新規投稿ならそこまで気にならないのですが、文章を修正する時に反映に時間がかかるのは見過ごせないデメリットです。

 また、複数の記事をちょっとだけ修正したい、という場合に、その都度CircleCIが走ってしまうので、Firestoreにデータを取得しに行く前に手動でキャンセルするという泥臭い処理が必要です。ここのオンオフを選べる機能をダッシュボードに追加できれば良いのかもしれません。

 とはいえ、タイムラグ自体は、CIツールによる静的化を選ぶ以上仕方ないところがあります。


デメリット2:費用が若干増えた

 最初はなるべく無料枠であるSparkプランの範囲に抑えようとしていたのですが、

 Cloud Functionsの外部サービスアクセス(特にPA APIとプッシュ通知)の制限が厳しかったことと、

 「万が一アクセスが急増した時にサービスが止まる方が困る」という判断から、無料枠もあるBlazeプランにアップグレードさせました。

 Firebase Functionsに関しては、外部サービス(Amazon PA API)へのアクセスを行い始めてからは若干のコストが乗るようになりましたが、こちらはPA APIを使わないサイトであれば無料枠でも問題ないかなと思います。


 で、実際運用してみると、実はFirebase Hostingの転送量制限が一番厳しい、ということがわかりました。私のNuxtビルド時のバンドルサイズ最適化が甘いのかもしれませんが。

 同じホスティングサービスであるNetlifyやGitHub Pagesが100GB/月まで無料なのに対し、Firebase Hostingは10GB/月

 360MB/日と、ちょっと少なめです。

image-20200506191955901

 日別の転送量については当月分しか確認できないので、5月のデータになりますが、このように、日によってバラつきはありつつも、500 ~ 600MB程度は毎日達しています。

 当サイトの1日の訪問者数がざっくり1日約600人なので、PWAキャッシュなどの効果も考慮しつつ、1人1MBと考えれば、まあ妥当かな、と思います。

image-20200506191539241

 1日3円くらいかかってます。月100円。


 どうしても無料にしたいのであれば、Cloudflareを間に入れることで転送量を減らすこともできると思いますが、

 Firebase HostingがデフォルトでCDN対応している中で、無料のCDNをもう1枚挟むことによる速度低下などのデメリットと天秤にかけた時に、月100円くらいなら払っても良い、と判断して、現在はこのままにしています。


まとめ

 Nuxt.jsとFirebaseを使ったサイトの開発は、正直とても楽しいです。静的化、PWA、プッシュ通知といったフロントエンドの最先端技術を簡単に取り入れることができ、UXもDXも目に見えて向上します。

 一方、WordPressはいろいろと時代遅れな部分もありますが、やはり個人ブログ向けのCMSとしては今でも最強の選択肢だと思います。

 WordPressの記事編集などのメリットをできる限り捨てずに、Nuxt.jsでサイトを作りたい、という人にとって、今回説明したような、WordPressとFirebaseを組み合わせて使う、というのは、もちろん特有のデメリットもあるものの、選択肢の1つとしてかなり良いのではないでしょうか。

 また、基本的な部分は最初はFirestoreとCloud Functionsというメイン機能だけで作れますが、機能を広げていけばFirebaseの機能を全て使うこともできるので、Firebaseのチュートリアルとして取り組む価値もあると思います。

 参考になれば幸いです。

References   [ + ]

1. ただし、1日のドキュメントの読み取り回数の無料枠上限が50000回なので、記事数が30000とかある人は別の方法を考えた方が良いかもしれません
2. 最初の数秒だけいいね: 0と表示されてしまう問題はありますが、大きな問題ではないので割り切っています
3. なのでその日初めてアクセスしたユーザーは表示まで少し時間かかると思います