🚀

Zennを支える技術とサービス構成

3 min read 33

Zennという技術情報共有サービスを作りました。有益な知見をシェアした開発者が、その見返りを得られるようなサービスにしたいと思います。気合いを入れつつも、時間をたっぷりかけて地道に育てていきます。

このページでは、Zennを支えている技術やサービスを紹介します。

フロントエンド

Next.js

フロントエンドにはNext.js(React)を使っています。開発当初はNuxt.jsを使っていたのですが、TypeScriptとの相性を考えてNext.jsへ移行しました。

技術情報共有サービスなので、主要な流入元はいずれ検索エンジンに落ち着くと予想しています。そのため、検索エンジンにインデックスしてもらいたいページはサーバーサイドレンダリング(SSR)しています。

動的コンテンツもキャッシュ

Next.js 9.4からIncremental Static Regenerationという最高の機能を使えるようになりました。ページにリクエストがあったときに、背後でビルドを行い、次回以降のリクエストではエッジサーバーからキャッシュされたデータを返します。これにより、ユーザーが投稿するコンテンツに対しても、高速なレスポンスを実現できるというわけです。

ただし、ユーザーの認証が絡むコンテンツに対してもキャッシュが効いてしまうため、少し工夫は必要になります。この工夫については別の記事にまとめる予定です。

グローバルの状態管理はRecoil

グローバルの状態管理には、思い切ってRecoilを使ってみました。Recoilは2020年5月頃にリリースされたばかりのReactの状態管理ライブラリです。
学習コストが小さく、React Hooksと合わせて使いやすいため今のところ気に入っています。

その他のライブラリ

主に今後も継続してメンテナンスされそうな有名どころのライブラリを使用しています。

  • styled-components:毎回迷いつつもなんだかんだで採用することになるCSS in JS。今後パフォーマンスの問題があれば置き換えるかも。
  • twemoji:AppleのOS以外の絵文字がZennのテイストにマッチしなかったため使用。
  • markdown-it:マークダウンをHTMLへ変換するために使用。
    など

Reactはモーダル(react-modal)やセレクトボックス(react-select)、スライダー(rc-slider)などよく使う機能ごとに定番のライブラリがあるのが嬉しいです。

バックエンド

Rails(APIモード)

バックエンドには「個人的に慣れている」「速く実装できる」という理由からRuby on Railsを選びました。ただ、今更ながらZennではExpressやNest.jsを使うべきだったと後悔しています。
理由は、フロントエンドでTypeScriptを使っていること(バックエンドでもTypeScriptを使って型を共有したい)、手軽なデプロイ先の選択肢がNode.jsと比べると限られることなどです。

本番環境のデプロイ

アカウント管理が煩雑にならないように、できる限りGCPのサービスを使用するようにしました。

Cloud Runは断念

開発環境にはDockerを使っているため、はじめはCloudRun(フルマネージド)にまとめてデプロイしていました。デプロイ体験は素晴らしかったのですが、実際にアプリを操作していると、バックエンドのレスポンスがまれに15秒以上遅延する現象が発生してしまいました。

原因はRailsの起動に時間がかかることのようです。公式ドキュメントの開発のヒントにも書かれているように、Cloud Runではサービス(コンテナ)の起動時間がレスポンスのレイテンシに直結します。

一部のGemを起動後に読み込むように変更してみたりもしましたが、大きな効果はなく、泣く泣くCloud Runを諦めることにしました。

バックエンドはGAE

ひとまずバックエンドにはGoogle App Engine(GAE)を使うことにしました。
Kubernetes Engine(GKE)の利用も少し検討しましたが、現状の単純なアーキテクチャではKubernetesを採用するメリットは小さいこと、GKEだとコストがかかること等からGAEを選びました。

また、以下のGCPサービスを利用しています。

  • Cloud Build:CIツール。自動テストと自動デプロイを実行。
  • KMS:秘匿情報の管理。Cloud Buildでのビルド時に暗号化された秘匿情報を復号。
  • Cloud Storage:ユーザーがアップロードする画像の保存先。
  • Cloud SQL:DB。PostgreSQLを利用。
  • Google Analytics Reporting API:各コンテンツのPVや滞在時間などの統計情報を取得するため。

フロントエンドはVercel

フロントエンドのデプロイ先はNext.jsとの相性からVercelを選びました。無料の範囲で十分な性能と機能が利用できます。デプロイは簡単ですし、特に設定しなくても静的ファイルをエッジキャッシュしてくれます。

その他に利用しているサービスなど

また、以下のようなサービスを利用しています。

  • Stripe:決済のため。
  • SendGrid:メール配信のため。
  • GitHub Apps:ユーザーが連携したリポジトリからコンテンツを同期するため。
  • Cloudinary:OGP画像を動的に生成するため。

OGP画像の生成については、Cloudinaryだと表現が限られるため、そのうちvercel/og-imageのような手法に置き換えようと考えています。

今後、開発をしていく中で変更があれば、追記or書き直そうと思います。

この記事に贈られたバッジ

Discussion

Recoli選択は思い切りましたね!!!

Recoilがちゃんとメンテされ続けることを祈ってます・・・

contributeしていきましょ!笑

リリースおめでとうございます。応援してます!

いいねボタンのアニメーションはreact-lottieなどではなく自作でしょうか?
(不適切な質問でしたらすいません)

自作です!お分かりかと思いますが表現はTwitterの影響を受けています

凄すぎます…!
素敵すぎて何度も押してしまっています。笑
アニメーション作成方法の記事も気が向きましたらぜひお願いします!
返信有難うございました

SVGをJSでちょっといじってる感じですね。たしかに!そのうち記事にします!

やったー!有難うございます!

技術選定のノウハウ共有はとてもありがたいです!応援します!!

リリースおめでとうございます!!

デザインめっちゃ好きですし、全体的に良いサービスだなと思いました!
これからのご活躍を陰ながら応援しています💪

リリースおめでとうございます!!技術選定のノウハウ参考になります!
Vercelは経験的に「転送量が多いからプランを変更して欲しい」メールがすぐ来る印象がありますね…

そうなんですね!現在VercelはHobbyプランですがお世話になりまくってるので、Pro(Team)プランに変更する気満々です。

完全に見落としてしまってました。
なお先週Proプランに変更しました。

コチラの記事の内容すごく参考になりました!応援しています。

Next.jsでZennのようなブログ投稿型のサイトをポートフォリオとして作成しようと考えております。記事の内容参考になりました。自分はもう少し簡単なものをNoSQLで作成しようと思いましたが、Zennではポスグレとのことでした。記事のデータなどはどのように持っているのか、差し支えなければ教えていただきたいです。

記事のデータについては、一般的な設計になっており、articleテーブルの中の1つのレコードとして持っています。
以下のようなカラムを持っており、単純にこれを表示している形になります。

  • title
  • body_markdown
  • body_html(HTMLへの変換後の文字列)

タグやLikeなどのメタ情報は別のテーブルに持たせています。

迅速なレスポンス、感謝します!大変参考になりました。ワードプレスなどのデータ設計に近いのかなと、勝手に想像しております。
度重ね質問で申し訳ありませんが、投稿時のエディタは自作でしょうか?

エディターには現在react-simple-mdeを使っています。(現在自作のものを開発中です)

いつも技術的な話からデザインまでめちゃめちゃ勉強させてもらっております。
ありがとうございます!

このページを拝見して、自分もnext.jsに魅力を感じ、始めてみることにしました!!
下記についての記事もとても楽しみにしております!
お体に気を付けて頑張ってください!

ただし、ユーザーの認証が絡むコンテンツに対してもキャッシュが効いてしまうため、少し工夫は必要になります。この工夫については別の記事にまとめる予定です。

Next.jsは本当に素晴らしいのでぜひ!
ありがとうございます!

Zennのサービス構成、とても参考になります。ありがとうございます。

自分も同じくRails+Next.jsという構成でのシステム構築を検討しているのですが、認証周りをどうするか悩んでいます。

Zennで使用しているライブラリや構成が分かると参考になりそうなので、もし良ければ教えていただけると嬉しいです。

そうですね。ISRと併用するために認証が必要なリクエストは基本的にクライアントでマウントされた後にブラウザから行っています。

質問失礼します。
Stripeは、Stripe connectを利用されているのでしょうか?
当方、ユーザー間で有料質問をしあえるサービスを開発中でStripeの利用を検討しているのですが、
ユーザーへの出金機能を携えるにはStripe connectがマストなのか?というところで詰まってしまっています。
もし差し支えなければ、教えて頂けると嬉しいです。

Stripe Connectは手数料の関係から使っていません。ただ、そのぶん入金の仕組みを自前で用意する必要があります。可能ならConnectにした方が良いとは思います。ちなみに以前これに近い質問へ回答しています。

ありがとうございます!
ユーザーの使いやすさのためにアプリ内はシンプルにしたいと思うのですが、セキュリティ面も調べたいと思います!

認証まわりについてもとても興味があります!

ログインするとコメントできます