ブログをNuxt+Ghost+Netlifyの構成にした

いい加減Ghostだと遅すぎるのでやめました。

ご存じの方もいるとは思いますが、GhostをおいていたブログはScalewayというサービスのVPS上で動かしており、リージョンがパリだったので(その辺しか選択肢がない)大変激遅状態だったのですが、いい加減やめたかったのでこの度フロント部分をNuxtに置き換えました。バックエンド部分は引き続きGhostを運用し、フロントはGhostのフックによりhtmlを生成する完全に静的なサイトになりました。

仕組み

  1. Ghostでブログを更新する
  2. Netlifyに通知が飛ぶ
  3. ビルドが開始される
  4. ビルド前にGhostのAPIを通してデータが取得され、それをビルドのリソースとして利用し、ビルド
  5. ブログが更新される
  6. やったね!

と丁寧に説明するまでもなく分かる方にはシンプルな構成になっています。

特徴とか

  • PWAの機能を取り入れた
    • とは言ってもホームに追加とかは要らないのでService workerによるキャッシュ+αに留めています。この辺の設定は結構入り組んでで挫折しかけたとも言う
  • ダークテーマ対応
    • media queryのprefers-color-schemeを使ってシステムのダークモード状況を見て勝手に切り替わるようにしてみました。
  • Algolia実装
    • ドキュメントサイトとかでよく見かけるアレです。検索自体はそんなに必要ないので実際のところ興味だけでつけましたが、中々いい感じに動いてるかと(挙動がややおかしいのに目をそむけながら)。

まだ実装してないところとか

  • SNS共有
    • 元からあったので実装したいけど面倒。面倒ってほど面倒なものでもないですが…。
  • ページャー
    • 記事呼んでる時に前後に移動したい。
  • AMP
    • 前は有効化にするだけで良かったのですが、フロント部分を自分で作るとなるとその辺の面倒を見なければならず、面倒で後回し。
  • tag/author周りのcoverURLの置き換え
    • やるだけではある。
  • その他細かい諸々
    • 疲れました。

他実装したいなとふんわり考えてるところ

  • 画像のキャッシュ及びハッシュファイル名による参照
    • 現状のシンプルなファイル名参照だと壊れた時面倒なのでそのへんどうにかしたい
  • 画像の圧縮(webp化)
    • ロードにかかる時間を極力避けたい

現状の実装だとfetch.tsが画像URLなどのコンテンツの調整を全て担っているのでそろそろリファクタリングしないとやばいよなあ…と思いつつも半分くらい飽きる状況。当初はAPI叩くだけでもっとシンプルだったのですけれど。

詳しい技術的などうでも良い話

最初の方はコンテンツのテキスト部分だけを引っ張ってくるようにしていましたが、やはり画像の表示の遅さがネックだったため、ビルド前に引っ張ってきたHTMLからcheerio(node.js上でjQueryライクでDOM操作が出来るライブラリ)でimg[src]を取り出し、自分のところの画像そうだったらNetlify側でもURLを参照できるように書き換え、ダウンロードキューに突っ込み、ダウンロードするようにしています。
とは言ってもビルド時に毎回ダウンロード発生したら嫌だなあ、ビルドキャッシュ的なものないかなあ…とNetlifyのドキュメントをさまよい、公式での記述は見つけられなかったのですが、Netlifyにはビルド時のキャッシュを作成したり参照出来たりする機能はあるようでビルドイメージのコードを参照しながら実装したら普通に上手く行きました。

htmlの生成にはnuxtのgenerateを使っていますが、これには2つ問題が有りました。

  • コンテンツが二重定義になる
  • serverMiddlewareはgenerate時に起動しない

前者はSSR(この場合だとHTMLダイレクト表示)とクライアントでの画面遷移によって表示されるコンテンツという2つの定義が一つのHTMLの中に定義され無駄がある。こちらはnuxt-payload-extractorを使って解決。

後者についてですが、そもそもなぜserverMiddlewareを使うのかというと、

  • 自分が管理するサーバーとは言えオーバーヘッドが大きいためghostのAPIを何度も叩きたくない
  • 全てのデータが含まれるjsonを用意したところでjs内でimportしたらassetに含まれてしまうので意味がない
    という理由があったため。serverMiddleware上でAPIを生やして、統合されたaxiosを利用して全データ取得のちフロントでfindしてほしいデータを取得という方式にしました(findのタイミングはAPI側でやるのが普通な気はしますが、URLパラメーター周りの扱いが面倒だっただけです)。こちらは自分でbuildModulesを書くことで解決しました。難しいことをしておらず、generate:beforeイベントをフックで引っ掛け、expressサーバーを立ててAPIのルートに位置するハンドラ(これもまたExpress)をマウントしてるだけです。ExpressがExpressアプリケーションをマウント出来る仕組みがあって助かりました。

ダークテーマについて。CSS FWにはBulmaを採用しています。最近ずっとこればかり使っていて、そろそろ違うの使いたいなあ…と思いつつも慣れてるし早く開発できるからこれでいっかというNuxtを惰性でやってるのと全く同じ理由で採用してます。
そのBulmaは標準でダークモードという概念には対応しておらず(ダークテーマを最初から考慮しているCSS FWはまだまだ少ないように思います)、変数などを置き換えるくらいでしかダークモードは対応することができなった…と思い込んでいたのですが、SASSは割と融通が効く書き方が出来て、@media (prefers-color-scheme: dark)内でbulma内で使う変数を上書きした上でbulmaをimportすることで普通に解決しました。ただ、見た目上の問題は解決していますが、CSSのサイズ的にはきっちり2倍になっているので(レイアウトの定義も二重)この辺は課題に感じています。他にも出力後のCSSを見るとmedia queryが想像以上に何回も記述されており、 css-mqpackerなどの利用も試みましたが、普通にデザインが崩壊したのでやめました。prefers-color-schemeを上手に利用するにはCSS Variablesを利用した方法が色の定義だけを上手く置き換えることができ、スマートなように感じますが、よくあるCSS FWはSASSの時点で色の計算を行っていることがあるようですので、sASSの変数にCSS Variablesを使うと行った手法は上手く行かないかもしれません(Bulmaはそうでした)。

コンテンツ配信について、v-htmlを使いたくない気持ちはめちゃくちゃありましたが、使わないとどうしようもない上、自分のブログなのでいいかということで利用しました。利用するのは良いのですが、CSSがついてくるわけじゃないので、そうなると/deep/セレクタを作ったCSSの付与やfetch時のクラス付与が必要になります。bulmaのコンポーネントは標準でもかなり綺麗でそのまま使うだけなら楽でサボることが出来るので一部はクラス付与、簡単に出来る部分は/deep/セレクタを使ったハイブリットな手法を採用しています。最初はbulma内にあるクラス定義のimport及びextendをして利用しようとしていたのですが、ダークテーマ作成時に色の定義が衝突して壊れることが発覚したのでやめにしました。


その他思うようなところは色々あるのですが、TIPSとして微妙だったりそもそも長くなりそうなのでこの辺で終わり。

しかしこれを作り始めたときは「データを取得して綺麗に表示するだけ!」と思っていたのですが、細かい要件やらを色々考えると中々に時間がかかってしましました。それだけ現代のアプリケーションにおいてフロントエンドの責務は大きくなっているわけで、これは自分の興味がある分野が発展している事実とも言えるのでそれが嬉しい一方で、しっかりとしたアプリケーションを作ろうとするとそれなりに時間がかかってしまうようになったというのも事実で複雑な気持ちです。