🚀

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

2020/09/08に公開
29

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 Cloud Runへ移行しました

ひとまずバックエンドには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

わたるわたる

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

わたるわたる

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

catnosecatnose

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

nitakingnitaking

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

uttkuttk

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

aliyomealiyome

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

catnosecatnose

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

catnosecatnose

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

ofa-chanofa-chan

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

本田宗一郎Bot本田宗一郎Bot

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

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

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

Y_uuuY_uuu

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

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

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

catnosecatnose

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

bubekitibubekiti

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

catnosecatnose

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

bubekitibubekiti

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