Closed111

Zennの技術まわりの質問があれば答えるよ

catnosecatnose

「あれどうやって実装してるの?」などなどの技術的な質問だけ受け付けます!
「こういう機能がほしい」みたいな要望はここではお答えしません。

リアルにサーモンリアルにサーモン

スクラップ機能、とても良さそうです。一利用者としてありがとうございます。

一般的な話かもしれないのですが、通知機構について伺いたいです。

  • 通知は様々な種類があると思うのですが(Likeされた、Commentされた等)、これらはどのような形式で永続化されているのでしょうか?
    RDBを使うやり方を単純に考えると、通知テーブルを用意して通知内容のオブジェクト(文字列?)をシリアライズしたものをtextカラムに保存したり、jsonカラムに保存したりするのかなと思いますが、もっと上手なやり方があるのかや、どのようなデータベースを使われているのか気になりました。

  • 永続化の際はイベント駆動などで何かしら非同期的な機構が走っているのかと予想しますが、実際に使われているシステムやアーキテクチャなども参考にしたいです

いろいろ書いてしまいましたが、可能な範囲でお答えいただけたら嬉しいです。

catnosecatnose

スクラップについてまだ周知していない段階で見つけていただきありがとうございますw
(どうやって気づかれたのか気になります)

通知はけっこう苦労した部分です。仰る通りオブジェクトの種類(Article, Book, Comment, User)が多いんですよね。

Zennの場合、notificationというテーブルがあって、通知が必要になったタイミングでnotifiable_typenotifiable_idに通知に関連するアイテムの情報を入れています。合わせて通知の種類と、通知先のuser_idなども保存しています。

例えば「あなたの記事にコメントがつきました」と通知したい場合、こんなイメージでレコードが追加されます。

notifiable_type: "Article"
notifiable_id: コメントがついた記事のID
user_id: 通知先のユーザーID
action: "commented" # コメントがついたという意味で
notified_from_user_id: 通知元のユーザーID(nullのこともある)

メール通知については、15分おきとかにバッチ処理でread:false(既読済みかどうかのフラグ)となっているものを(ユーザーの通知設定がONになっている項目のみ)一括で送るようにしています。

現状こんな感じですが、もっと良い方法はあるような気がしてます。

catnosecatnose

永続化の際はイベント駆動などで何かしら非同期的な機構が走っているのかと予想しますが、実際に使われているシステムやアーキテクチャなども参考にしたいです

バックエンドにはRails(on App Engine) + PostgreSQL(on CloudSQL)を使っています。Railsだと非同期処理についてはSidekiq(+ Redis)を使うのが一般的だと思うのですが、サービスのリリース直前までApp EngineではなくCloudRunにデプロイしていた関係で、Sidekiqなどにより非同期処理は使っていません。

関連
https://stackoverflow.com/questions/60297681/rails-jobs-with-sidekiq-in-google-cloud-run-not-working/60303701#60303701

現時点では通知なども同期的に永続化しており、負荷の大きいタスクは定期的なバッチ処理で対応していたりします(タスク用のApp Engineインスタンスを作っています)

ただ、既に限界は感じているので、このあたりは今後着手しようと思っています。

リアルにサーモンリアルにサーモン

勝手に使わせていただきすみませんw
catnoseさんのユーザーページのコメントタブでたまたま発見して、新機能のようだったので少し興奮してしまいました笑

なるほど!テーブル設計までありがとうございます、参考になります。バッチ処理と同期処理を使い分けられてるんですね。

ついでに思い出したので、もう一つだけ。Like判定に特化したAPIが用意されているようで最初ほうっと思ったのですが、この辺りはISRとユーザー認証をうまく共存させる工夫でしょうか? UGC型のサービスでISRを活用するための一つの指針となりそうな解決法ですね。

catnosecatnose

なるほど!ミスってしばらくコメント一覧に出ちゃってたんですよねw

まさにその通りです。ごく一部を除いて認証が絡む部分はクライアントでマウントされてからリクエストしています。「Incremental Static Regeneration」のようにガッツリとページをキャッシュする場合、そのあたりが少し面倒になりますね…。

一応他の手段として、キャッシュするデータに「LikeしたユーザーのID一覧」を含めて、クライアント側でログインユーザーのIDと一致するか検証する方法も検討したのですが、その場合Likeした直後にページをリロードするとLikeしていないことになってしまうという問題があり、このようにしています。

Local Storageなどを使えばクリアできる問題とは思いますが、あまり複雑にしたくなかったので現状こんな感じにしています。同じ構成のサービスがどうしているのか気になるところです。

リアルにサーモンリアルにサーモン

なるほど〜面白いですね。シンプルで良さげな方法だなと思いました。まだISRを活用したサービスって少なそうなので今後ベストプラクティス的なものが確立されていくのか楽しみですね。

色々とご教授ありがとうございました!サービス共に末長く活用させていただきます。

kkiyama117kkiyama117

zenn-editorやその他の部分で現状テストはどれくらい用意してらっしゃいますか?また使用予定のlibraryなどありますか?
Contributeしたい時に、どうしてもテストが無いと不安になってしまうので、可能なら暇な時間にテストを書いてプルリクエストを投げたいのと、純粋に自分が初心者なので社会人の方々のそういった環境が気になる、ということで質問させていただきました。

catnosecatnose

Webアプリ(zenn.dev)の方はバックエンドを中心にテストを書いています。カバレッジは測っていませんが、主要な部分はだいたいカバーできているんじゃないかと思います。

すみません、zenn-editorではまったくテストを書いてません。書きたいと思っていたのですが、色々と急いでいたので…。そして書こうと思っているのですがまだ着手できていません。

純粋に自分が初心者なので社会人の方々のそういった環境が気になる

これは社会人として参考にするべき例ではないです。個人開発だからこんな感じですが、本来最初から書くべきだと思います。
(もうすぐ個人開発じゃなくなりそうなのでちゃんと書く予定です。はい)

可能なら暇な時間にテストを書いてプルリクエストを投げたい

ありがたいお話ではあるのですが、あくまでもzenn-editorzenn.dev依存のものなので申し訳ないです。もっと幅広く使われているOSSへ貢献した方が実績/勉強になると思います。
時間が出来たらテストは書く予定です。すみません、はい。

(「時間は作るもの」ですよね。すみません、はい。)

kkiyama117kkiyama117

お忙しい中すみません、ご返事いただきありがとうございます
catnoseさんがほぼ一人でやっていらっしゃるとの事ですので、より優先順位の高いサービスの重要なところから実装していくのは当然の事だと思います。急かすみたいな形になってしまいすみません。

ありがたいお話ではあるのですが、あくまでもzenn-editorはzenn.dev依存のものなので申し訳ないです。もっと幅広く使われているOSSへ貢献した方が実績/勉強になると思います。

純粋に自分が普段使わせていただいているもの/見ているものに対しまずは貢献/勉強したいなと思っていました、ただそれこそお忙しいでしょうし個人のプルリクエストに時間を割いていただくのも申し訳ないのと他の部分に時間を割くべきというのがありますよね、ごめんなさい。

catnosecatnose

いえ、プルリクについてはありがたさしかないです!ただあまり時間を取っていただいちゃうと、手数料を取っている商用サービスなのに対価をお支払いできずすみません…ってなりますw

catnosecatnose

後から見た人のために追記)zenn-editorもテストちゃんと書き始めました

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

記事や本の検索機能、サジェストについてどのように実現させているのか気になります!
また、PostgreSQLを採用した理由も可能であれば伺いたいです。(自分がmysqlしか使ってこなかったので・・・)

catnosecatnose

検索は現在はバックエンド(Rails)のRansackと呼ばれるもので行っています。できることが限られているのでそのうちAlgoriaかElasticsearchに置き換えたいと思ってます。

関連記事についてはタグを見ています。

PostgreSQLを選んだ理由は開発当初Herokuにデプロイすることが選択肢にあったからです。

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

お忙しい中、ありがとうございます!
Algoriaはよく聞きますが、やはり色々やれることが多いんですね...。
PostgreSQLにつきましてもご回答ありがとうございます。
いつも本当に参考にさせて頂いております。

JJJJ

Next.jsのSSRということで、 getServerSideProps を実装していると思うのですが getServerSideProps でのランタイムエラーは上位コンポーネントの componentDidCatch で取れない中で、そこら辺のエラーハンドリングについてどうやって抽象化したりしてるか知りたいです!

catnosecatnose

getServerSideProps使おうと思っていたのですが、色々比較してみたところgetInitialPropsの方がページ遷移時の体験が良かったので現状ではgetInitialPropsを使っています。
(エラーハンドリングはどちらも同じようになると思います)

イメージ的には以下のようなことをやっています(色々抜粋)。

type Props = {
  posts: Post[];
  errorCode?: number;
}

function Page(props: Props) {

  if(props.errorCode) return <Error status={props.errorCode} />; // エラー用のコンポーネント

  return <div>色々</div>
}

Page.getInitialProps = async (ctx) => {
  try {
     const { posts } = await requestSomething();
     return {
        posts,
     }
  } catch(err) {
     const errorCode = err.response?.status || 500;
     return {
        errorCode
     }
  }  
}

export default Page;

ここでは省いていますが、エラーコード周りの型はTypeScriptのユニオン型で「あるかもしれない」プロパティを表現するときのTipsのような形で表現しています。

JJJJ

やっぱそうですよね。cDCはランタイムだけで起きた予期しないエラーにより画面が真っ白になるために置いておくが、基本的にstatusCode的なのをreturnしてコンポーネント側でnext/errorを返すみたいな実装ですよね...

ありがとうございます!少し自信が持てました!

YusukeYusuke

素晴らしいサービスとそのオープンな情報を大変ありがたく参考にさせてもらっています。

認証周りについてnextjsとbackend apiを利用するパターンでどのように行うべきか、なかなか情報が出てこず迷っています。
nextjs とnextjs の api route を利用する形なら next-auth が便利そうですが、rails apiとnedxtjsを使われているzennはどのように実装されているんでしょうか?
(fastifyあたりを使ってみたいのですが、ぜひ参考にさせてもらえればと思い質問しました。)

catnosecatnose

nextjs とnextjs の api route を利用する形なら next-auth が便利そう

同意です。小規模のアプリであればこの形が良さそうだと思っています。
blitz
を使ったりするとそのあたりも気軽にできたりするのかも…?(まだ触っていないので分からないですが)

僕なら今小規模なサービスを作るならDBはFirestoreやMongoDB Atlasとかにして、認証はFirebase Authenticationに任せてしまう気がします。

ZennのRails APIは2020年12月時点ではdeviseというgemを使ってcookieによるセッション認証を行っています。セキュリティのためにcookieはhttp-onlyにしたりとごく一般的な実装です。

ただ現在NestJSへのリプレイスを検討しています。その場合はpassportという定番ライブラリを使うことになりそうです。

コウダイコウダイ

的はずれな質問でしたら申し訳ないです。
バックエンドとフロントエンドの通信間のセキュリティについてですが、どのようにしているのでしょうか?
apikeyやcorsなど、、、

catnosecatnose

認証・認可の仕方については上記のコメントの通りです。
セキュリティという観点で大まかなにやっていることは

  • 通信はhttpsに限定する
  • cookieはhttp-onlyにする
  • CSRF対策のためcookieはSameSite=Laxにする

などですね。

もちろんCORSの設定もしています。

suzukeysuzukey

アイコンなどはインラインSVGを使い、CSSModulesのclassでスタイルを当てていると思うのですが、
SVGをアイコン毎にコンポーネント化しているのか、babel-plugin-inline-react-svgなどでインポートしているのか、その他か、どのように管理されているかを教えていただきたいです。
また、その方法でクラスの当て方はどうされているのでしょうか?

Nextjsを利用しているプロジェクトでSVGの管理に悩んでおりますので質問させていただきました。

catnosecatnose

react-svg-loaderを使っています。使い方はごく普通で以下のような感じですね。

import SvgPencil from '@assets/svg/pencil.svg';

<SvgPencil width={50} height={50} class={styles.editIcon} />
suzukeysuzukey

とても助かりました!
お忙しい中、ありがとうございます!

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

Zennの記事ページはISRではなくSSRでしょうか?
ISRだと思っていたのですが、記事の編集が即時で反映されたので気になりました。

catnosecatnose

遅くなりすみません。記事ページはISRです。著者本人によるアクセスの場合にはクライアントでのマウント後に再フェッチしています。

まっつんまっつん

2つお聞きしたいことがあります!

  1. Editor部分でMonacoではなくCodeMirrorを利用している理由が知りたいです。
  2. ユーザー認証でAuth0のような認証Saasを利用されていないのには理由がありますか?

よろしくお願いします!

catnosecatnose

エディターライブラリを色々見た結果、react-simple-editorを使うことにしました。その中でCodemirrorが使われているわけなので、僕がこだわって選んだわけではありません。とはいえCodemirrorはAPIが豊富で個人的に気に入っています。

認証系のSaasを使わなかった理由はサーバーサイドで十分使いやすいライブラリがあったからです。あとAuth0はわりとお金かかりますよね。

まっつんまっつん

なるほどですー!
Auth0はユーザー多いとかなり高くなりますよね。。
ご回答ありがとうございます!とても参考になりました!

mozisanmozisan

いつもZennにはお世話になってます!

Next.jsについて一点お聞きしたいことがあります。
ベーシックにページコンポーネントを作ると、ページ間で同じコンポーネントを使っててもページごとに全ての要素が再レンダリングされるため、.explore_navLinkでやっているようなトランジションは実現できないと思います。
簡単にどうにかするなら_app.tsxに共通コンポーネントを置けばいいとは思うのですが、なんとなく別のやり方をしてらっしゃる気がして、教えていただけたら幸いです。
(他にも何パターンか自分の中でやり方は見つけてはいるのですが、イマイチやり方にしっくりこず。。)

catnosecatnose

良い質問ですね…。その部分は試行錯誤したところで、最終的には_app.tsxから読み込むLayout的なものを作って実現しています。そのうち記事にします。

mozisanmozisan

お返事いただきありがとうございます!

_app.tsxで実現されていたんですね。記事楽しみにしています!

(自分の文見直したら「再レンダリング」と書いていましたが「再マウント」でした 😞 意図が伝わっていてよかったです)

yuyao17yuyao17

技術周りではないですが、UIに関して一つ質問があります。
英語と日本語が混在していると思うのですが(Add New だったり)、その辺ってどうやって使い分けているのでしょうか?ここは日本語、ここは英語、、みたいなことってそもそもしているんですかね?

よろしくお願いします。

catnosecatnose

分かりづらくてすみません。

まず、できれば全部英語にしたいという思いがあります。その方がきれいに見えるので。ただ、ユーザーさんが何の操作なのか分からない可能性があるなと感じた部分は(本当はあまりしたくないのですが)日本語にしています。

ちなみにリリースから少しずつ英語を日本語に書き換えていっています。最初はパッと見たときにカッコいい方が目を引いてユーザーが集まりやすい気がしますが、だんだんと見慣れてパッと見のカッコよさより分かりやすさの方が重要になるんじゃないか、という考えです。

yuyao17yuyao17

なるほど!!ありがとうございます。分かりづらいとかでは全くなくて、自分もプロダクト作るときに表記に迷うところでもあり、zenn ではどうしてるのかな〜ってふわっと思っただけです!!お時間いただきありがとうございました!!

JJJJ

Zennに限ったことではないのですがZennも対応をしてたので2点質問があります。

1: 例えば記事の https://zenn.dev/catnose99/articles/c7754ba6e4adac のページで、この記事では

<link rel="canonical" href="https://zenn.dev/catnose99/articles/c7754ba6e4adac">

の設定がしてありますがこれはなぜ行っているのでしょうか?

2: またこのscrapのページのURLは https://zenn.dev/catnose99/scraps/468bedaab6dbe3ecfcae ですがこのページに対しては canonical の設定を行っていません。この2つの差はなんなのか気になりました!

catnosecatnose

1はこのツイートのとおりです!

  • utm とかのクエリパラメータが色々つくケースがあるので統一したい
  • 明示的に防がない場合 PaaS などが提供する http://web.apphttp://vercel.app みたいなドメインも一応あるので重複扱いにならないようにしておきたい

2は多分ミスです。見落としてました…。ありがとうございます。

matamatanotmatamatanot

Vercelを活用しているかと思います。Vercelに関していくつか質問があります。

  1. PlanはProとEnterpriseのどちらでしょうか。
  2. Vercelから別サービスへの移行などは予定されていますでしょうか。
  3. (Pro Planの場合)BandWidthが1TBという上限がありますが、何か削減のための工夫などは実施していますでしょうか。

技術的なことだけでなくビジネス的な要素も含みますが、可能な範囲で回答よろしくお願いします。

catnosecatnose
  1. Proプランを使っています。
  2. 他サービスへの移行は予定していません。やはりNext.jsの色々な機能を使う際の検証コストが小さくて楽なので…。ただ危機感は持っていて、いざというときは移行する心の準備はできています。
  3. そこなんですよね。アクセスが増えてもEnterpriseの規模ではないので1TBに納める必要があります。現時点ではまだ余裕がありますが、増えないように気をつけています。たとえば以下の部分ですね
  • next/imageは使っていない。使うことになってもCloudinaryを通す予定
  • API Routesを使えば楽な部分でもCloud Functionsを使うようにしている

あとはアレですね。Proプランで収まらなくなったときにはクラスメソッド社の力を使えば(交渉などで)何とかなるかもしれない…というわずかな希望も持ってます笑

matamatanotmatamatanot

回答ありがとうございます!

  1. Proだとかなりコスパいいと感じますね。Password Protectionが割高ですが。
  2. 同感です。例えば今後も React Sercer Components + Vercel + Next.js などの相性などを考えると、なるべくVercelを活用したいと考えています。
  3. Enterprise Planについて問い合わせをVercelサポートにしているのですがかなり割高と感じました。なるべくPro Planに抑えたいと考えていたので助かりました。next/imageやAPI RoutesはやはりBandwidthを大きく消費するのでこの観点だとなるべく避けたいですよね。

Proプランで収まらなくなったときにはクラスメソッド社の力を使えば何とかなるかもしれない…

Enterprise Planの国内事例を殆どしらないので楽しみにしてます!

bernbern

Zennを作るのにかかった時間を知りたいです...! リリースでここまで完成度が高い、個人開発のサービスを初めて見ました。よろしくお願いします。

catnosecatnose

2019年の夏頃に開発をはじめました。フルタイムで開発して2020年の1月頃に一旦完成したのですが、そのあたりで第一子が生まれたのでリリースを見送りました。

その後は育児をしながらゆるーーく開発して2021年の9月にリリースした感じです。
(ゆるーく開発してる頃にNuxt.jsからNext.jsへの移行や、GitHub連携機能などをつけました)

bernbern

ご回答していただきありがとうございます。素晴らしいの一言です👏👏👏 こんな素敵なサービスを世の中に生み出してくださり感謝です。

ryo_kawamataryo_kawamata

いつも利用させて頂いています!素敵なサービスありがとうございます!

Zennの機能の技術的な何かというよりは、技術選定についての質問です。

ZennのCSSはCSS Modulesを利用されていると以下Scrapで拝見しました。

経緯的には styled-components ▶ linaria(試すのみ) ▶ CSS Modules となったようなのですが、その際にVercelが開発していてNext.jsに標準で組み込まれている Styled JSX は選択肢には上がらなかったのでしょうか?
もし、上がった上で候補から外したのならその理由など知りたいです :pray:
(新規でNext.jsのサービスを検討中でそのCSS設計の参考にさせて頂きたく。。)

catnosecatnose

ありがとうございます!

一応試したような気がしますが、ほとんど検討しなかったですね…。今思うと悪くない選択肢かも?

ただ、コンポーネントのjsx部分が膨らむのは気になりますね。そこも分割出来るのかもですが。うーん、もう少しちゃんと検討するべきでしたw

ryo_kawamataryo_kawamata

うおお!早速回答ありがとうございます!!感謝です。

そうだったのですね。

ただ、コンポーネントのjsx部分が膨らむのは気になりますね。そこも分割出来るのかもですが。

実は外出しも出来るようで、その問題は解消できるかもです。
https://github.com/vercel/styled-jsx#external-styles

ReactはCSS周りの選択肢多くて迷いますね〜。。
catnoseさんの記事も参考に色々検討したいと思います。

nabeyangnabeyang

zenn-cliとzenn.devとの整合性はどうやって取っているのでしょうか?気を付けないと、zenn-cliでローカルで編集したときと、本番に上げたときで表示が異なることがありそうなのですが、その辺、どのように頑張っているのか教えて下さい。

catnosecatnose

コンテンツ用のCSSやMarkdownパーサーはnpmにpublishすることで共有するようにしています。運用面ではnpm publishした直後に本番環境のAPIにデプロイするようにしています。

zenn-cliについてはいずれにせよユーザーさんがパッケージをアップデートするまで反映されないため、完全に整合性を取ることは諦めて、大きな変更があるときのみTwitterで周知することにしています。

コウダイコウダイ

zennでは、ssgを使用してるページはありますか?
また、CSR,SSR,SSG,ISRそれぞれどんな風に使い分けていますか?

catnosecatnose

※ 略語に馴染みがない方のために括弧で正式名称を書いておきます。

利用規約やプライバシーポリシー、AboutページなどはSSG(Static Site Generation)を使用しています。使い分けとしては

  • ダッシュボードなどのログインユーザーのみ閲覧ができるページ => CSR(Client Side Rendering)
  • SEO上重要かつページネーションが必要な一覧ページ => SSR(Server Side Rendering)
  • スクラップのように更新頻度が高いページ => SSR
  • 記事や本などの速度が重要なページ => ISR(Incremental Static Regeneration)

という感じです。

akaboshinitakaboshinit

次いでリプライ失礼します

  1. 現在はどのようなページ構成になっていますか?

Next.jsアプリをVercelからGoogle Cloudに移行した話

キャッシュを廃止した記述
なお、記事や本の詳細ページなどでは、キャッシュの設定を誤ったときのリスクの大きさからキャッシュ自体を廃止することにしました。(ソースコードの見通しが良くなった!)

少し前の記事ですが、zennではISRを廃止し、stale-while-revalidate(SWR)などを使って代替的に実装していくと書かれていて現在のページの構成を教えていただきたいです!
ちなみに、個人開発で有料ブログサービスを開発中なので特にBooksの構成が気になります!

  1. 有料Booksを非購入者では閲覧できなくする工夫を教えてください

有料ページを下手にISRなどでデータ取得してしまうと非購入者でもdevtoolで内容を見ることができてしまうなどの問題があり、zennの有料Booksではどのように購入者,非購入者を判断し、表示を分けているのか教えていただけるとありがたいです!

CookCook

決済周りについてのご質問です。
Zennは本の売買やサポートを行う際に、Stripeを利用して決済などが行われている認識なのですが、この時にどのようなStripeの機能を利用して、決済から振込まで行っているのでしょうか?

  • Paymentsのみで、売上を運営が管理し、決済から振り込みまで実行
  • PaymentsとConnectを利用して、売上をユーザーが管理し、決済から振り込みまでを実行

こちら可能な範囲でお答えいただけたら嬉しいです。

catnosecatnose

現状では手数料などの関係から「Paymentsのみで、売上を運営が管理し、決済から振り込みまで実行」としています。

※ ちょっとした支払い・入金管理システムを独自で構築しています。

CookCook

ご回答ありがとうございます!!
参考にさせていただきます!

Masaki MuranoMasaki Murano

こんにちは、RailsのAPIについて質問させてください。
https://zenn.dev/catnose99/articles/zenn-dev-stack#rails(apiモード)

今は、RESTfulなAPIで実装されてますか?GraphQLは使う検討などされてましたか?今の実装を選択された理由などありましたら教えていただければと思います m(_ _)m

catnosecatnose

はい、RESTな感じのAPIにしています。考え方的にはWeb API: The Good Partsにできるだけ則ろうとしている感じです。

途中でRestfulとパフォーマンスの両立が難しくなってきたのでGraphQLも検討したのですが、導入のハードルの高さで躓きました…。

ryo_kawamataryo_kawamata

記事のTopicsのicon画像表示について質問です!

素朴な疑問なのですが、記事のTopics文字列でのicon画像表示ってどういうロジックになっているのですが?
topicsの各技術要素の文字列対画像パスのハッシュをどこかで持っているかと思うのですが、どんどん出てくる技術要素に合わせて更新していくのがなかなか大変そうだなと思いまして。。
どのような解決手段か気になりました。

catnosecatnose

Zennのアイコン画像の登録は、基本的に「ユーザーが申請して管理者が承認する」仕組みになっています。

例:Reactのアイコンの変更申請ページ

申請が送られると、topic_change_proposalのようなテーブルにレコードが挿入されます。その中にCloudStorage上の画像のファイル名が格納されています。

申請を承認するとtopic_change_proposalの画像ファイル名の値を、topicテーブルの画像ファイル名のフィールドにコピーします。そんな感じで最新の画像のパスをtopicテーブルにもたせています。

ryo_kawamataryo_kawamata

Zennのアイコン画像の登録は、基本的に「ユーザーが申請して管理者が承認する」仕組みになっています。

なるほどーー!ありがとうございます!!
申請性だったとは知らなかったです。この仕組みは良いですね
早速いくつか申請してみようと思います!

catnosecatnose

ですです!今はスマホからは申請できないので気づきにくいかもですね。
ありがとうございます!!

sugasakisugasaki

Zennを支える技術とサービス構成を読んでの質問させてください。

GAEを使っているとのことですが、
クライアントとGAEの間にロードバランサは入っていますか?
それともZENN全体のリクエストをGAE1インスタンスで処理を捌いていますか?

負荷分散の話が出てきてなかったので、どうやって捌いているのかな?と気になりました。

catnosecatnose

ロードバランサを自分で入れたりはしていないですね。GAEのオートスケール設定を使っています。
一定の条件を超えると自動でインスタンスが増えるようになっています。

具体的にはapp.yamlAutomatic scalingのあたりで設定をしています。

sugasakisugasaki

なるほど、オートスケールだけで結構捌けるんですね。
ありがとうございます!

sugasakisugasaki

そういえば、時々表示に時間がかかる時が、インスタンスが増えてる時って感じでしょか?

catnosecatnose

今はリアルタイムで(フロントエンド側が依存している)AWSの障害が起きているので表示に時間がかかりますね…

あと2020年12月頃にオートスケール時のレイテンシと料金対策のためにStandard環境からFlexible環境へと移行しました。

https://zenn.dev/catnose99/articles/f99ea2a8b985b2

sugasakisugasaki

昨夜全体的に遅いなーと思ってたのは障害だったんですね。
普段の表示は早いですものね。失礼しました。

Standard環境からFlexible環境へと移行記事、読ませていただきます。
ありがとうございます!

雪猫雪猫

URL の設計について聞いてみたいです。
ユーザーページが /[user_name] なので Zenn のページである /article/search などを予約語にしていると思うのですが不都合はなかったのか、またそうした理由が気になりました。

catnosecatnose

はい、仰る通りです。不都合というのは「ユーザーページを/[username]にすることで発生する不都合」という理解で合っていますでしょうか?(この理解で回答させていただきます)

開発者側からするとユーザーページを/users/[username]ではなく/[username]にすることで面倒な問題が色々と発生しています。新しいページを追加するときには予約語をチェックしないといけないですし、ルーティングもやや面倒です。

とはいえ、ユーザーページのURLをできる限り簡潔にしたい、各ページのURLも推測しやすいものにしたいという考えからこのようにしています。(記事一覧は/[username]/articlesで、記事自体は/[username]/articles/[slug]など)

という感じの回答になりますが、質問の主旨にお答えできていますかね…?

雪猫雪猫

回答ありがとうございます。(分かりづらくてすみません、意図通りの回答になってます)

面倒な問題が色々と発生しています。

やっぱりそうですよね。

ユーザーページのURLをできる限り簡潔にしたい、各ページのURLも推測しやすいものにしたい

とてもよく分かります。
面倒な問題が発生してでもこちらを優先したとのことで参考になりました。
このへんはどうしてもトレードオフになってしまうんですね。

nabeyangnabeyang

GCPとVercelを使っているとのことですが、AWS(VercelはAWSに依存しているという想定で書いています)とGCPどちらかで障害が発生するとzennの方で何かしら影響を受けるという理解で合っているでしょうか?

catnosecatnose

合っています。GCPのAppEngine、AWS Lambda、Vercel自体のいずれかで障害が起きると影響を受けます。

tkc310tkc310

綺麗なUIでいつも個人ブログの参考にさせていただいています。

Articlesの目次はサーバサイド(or BFF)で処理されていると思いますが

  1. 文章中のheadingタグ集計・目次化する実装はライブラリを使われていますか?自前での実装でしょうか?
  2. 画面位置が該当headingに到達した場合、太字になる実装についてもライブラリをお使いの場合はお教えいただけるとありがたいです。(intersection observer系のライブラリでしょうか)
catnosecatnose

tocbotというnpmパッケージを使っています。

headingタグから目次を自動生成してくれる + 現在見ている部分の目次に対して指定したクラス名を付与してくれます(activeLinkClass )。そのクラスに対してfont-weight: 700とか指定するだけで太字にできます。

tkc310tkc310

早々にご回答ありがとうございます。
npmパッケージの使い方についても、解説ありがとうございます。

恋螢恋螢

横から質問失礼致します。tocbotかなり便利ですね。1つ質問なのですが、zenn ではスクロールした時に目次のstickyの方も正しくスクロールされていますがデフォルトの tocbot では sticky のスクロールはされないかと思います。こちらはどのような形で実現されているのでしょうか?

Nobuyuki ItoNobuyuki Ito

素敵なサービスをありがとうございます!

以前別の方の質問に

現状では手数料などの関係から「Paymentsのみで、売上を運営が管理し、決済から振り込みまで実行」としています。

とお答えになっておられましたが、各ユーザへの振り込み周りはどのような仕組み(サービス。。。というか銀行の提供する何かになるのでしょうか)を使ってらっしゃるのでしょうか?
答えていただける質問かわかりませんが、是非知りたいです。
どうぞよろしくお願いいたします。

catnosecatnose

あまり詳しくは書けないのですが、銀行のオンライン振込を独自のJSを使って効率化しているような感じです(あまり参考になる回答ではなくすみません)

坦々狸坦々狸

こんな所があるとは知らずに記事のコメントの方に質問を連投してしまっていました
大変失礼しました。

https://zenn.dev/catnose99/articles/cb72a73368a547756862#comment-0b9502e2b233d8

色々調べた結果をつらつら書いてしまい内容が冗長になってしまっているのですが要約すると以下のように古いRSSが取得されるケースがあるという内容です

タイミングによりますがこんな感じでずれます。記事内容もこの時間相当の記事でした
$ diff \
> <(xmllint --xpath "//lastBuildDate/text()" <(curl -s https://zenn.dev/tantan_tanuki/feed)) \
> <(xmllint --xpath "//lastBuildDate/text()" <(curl -s https://zenn.dev/tantan_tanuki/feed))
1c1
< Thu, 08 Apr 2021 15:08:51 GMT
---
> Fri, 09 Apr 2021 08:42:43 GMT

何かアドバイスをご教授願えないでしょうか?

catnosecatnose

ユーザーごとのRSSフィードについては、負荷の都合により24時間CDNにキャッシュするような仕様にしておりました。そのため最大1日前のRSSフィードが表示されてしまっていたのだと思われます。ご指摘を受けてこの時間は長すぎると考えたため、さきほどこのキャッシュの時間を6時間に変更しました。

記事の更新後、しばらくは古いRSSが表示されてしまうことがありますが、ご理解いただけますと幸いです。

坦々狸坦々狸

おぉ、仕様だったのですね。
もっと簡単に対応できるものを想定していました。
他の人が気にしてなさそうな事だったのにお金の掛かりそうな対応して頂いて申し訳ありません。

Eringi_V3Eringi_V3

Next.jsのファイルベースルーティングのID重複の制御についてどのように実装しているかお聞きしたいです。

例えば、ユーザーの記事一覧ページ(https://zenn.dev/eringiv3)があります。
これはおそらく pages/[userId].tsx のようなファイルで実現しているのかなと思っています。

ただ、URLの階層がユーザーの記事一覧ページと被っているページ(例えば https://zenn.dev/about )があります。
その場合、ユーザーがIDを変更するときに「about」という文字列は選択できないようにするバリデーションを実装する必要があります(ユーザーがaboutというIDを設定したら、そのユーザーの記事一覧ページが見れなくなってしまうから)。
このバリデーションについて、Zennではどのような実装を行っているのでしょうか?

ユーザーが使用することのできないID一覧リストのようなものをnext buildの直前に生成しておいてpublic配下に置いておく、などでフロントエンドのバリデーションはなんとかなりそうですが、サーバーサイド側でも真面目にやろうとするとちょっと大変そうです。

catnosecatnose

返信がとても遅くなってすみません!(見落としてました)
Zennではあらかじめ一部の単語をユーザー名として取得できないようにして、ご指摘の問題を回避しています。「reserved usernames」とかでググるといい感じのリストが見つかって楽だったりします。

bubekitibubekiti

質問失礼します!
フロントはNext.js、バックエンドはrailsと伺っていますが、
開発時のリポジトリは分けていますでしょうか?
自分もこの技術でサービスを作ろうとしていて、railsはdockerに乗せてAWSにデプロイ、Next.jsはvercelを考えているので、型の共有もないので別々のリポジトリで良いかなと考えているのですが、
差し支えなければリポジトリ構成を教えていただきたいです。

catnosecatnose

僕はフロントエンドもバックエンドも両方書くので一つのリポジトリにまとめて管理しています。チームが大きくなって、メンバーの役割分担が明確になったときはリポジトリを分けるべきかなーと考えていますが、今のところは予定なしです。

ちなみにテストやデプロイが非効率にならないように、フロントエンドのファイルだけが更新されたときはバックエンドのCI/CDはスキップされるように設定しています。

bubekitibubekiti

ありがとうございます!
チームの規模なども考慮必要ですよね、、
参考にさせていただきます!

Yuta KobayashiYuta Kobayashi

slugはサイト全体で(記事や本などのコンテンツの種類ごとに)ユニークにする必要があるようですがもし重複してしまった場合はどのような処理をしているのでしょうか?

https://zenn.dev/zenn/articles/what-is-slug

catnosecatnose

ユニーク制約をかけているため、slugが重複する場合はレコードを作成できないようになっています。

Chris JonsonChris Jonson

お忙しいところ質問失礼します!

Zennには1. 「Zenn運営用のGoogle Analytics」、2. 「ユーザが設定できるGoogle AnalyticsのトラッキングID」の二種類がありますが、1はGTMで、2はgtagで送信しているのかと思うのですが(間違っていたらすみません。)、なぜ2種類の送信の仕方をしているのでしょうか?

また、2はそのユーザのURL空間以外(例: /catnose99*以外)は発火しないようになっていますが、特にnext/routerでの遷移の時、どのように制御されていますか?

差し支えなければご教授いただきたいです!

hikaru666hikaru666

next初心者のため、検討違いな質問であったらすみません。
こちらのスクラップに

バックエンドにはRails(on App Engine) + PostgreSQL(on CloudSQL)を使っています。

とのコメントされていましたが、
ServerSidePropsやAPIRoutesから(Prismaやpg-promiseなどを利用して)直接DB接続をしないのは何故でしょうか。
Rails(App Engine)を挟むメリットがあれば教えていただきたいです。

catnosecatnose

返信が遅くなりすみません。
これはサービスのビジネスロジックの所在をバックエンド(Rails)に統一するためです。Zennではバッチ処理や非同期処理なども多く実行されており、これらを全てNext.jsのAPI Routesで行うのは(できなくはないと思いますが)非効率な部分が多く出てくると考えています。
また「記事詳細ページだけはgetServerSidePropsでDBから直接取得する」ということもできなくはないと思いますが、RailsとNext.jsのそれぞれで「どのように記事を取得するか」や「どのように公開可能な記事だとみなすか」というロジックを持たせることになりメンテナンスが難しくなります。

とはいえこのツイートのように、今1から小〜中規模なWebサービスを作るのであればPrismaを使ってgetServerSidePropsなどから直接DBに接続するような方法を取る気がします。

syotarosyotaro

記事slugなのですが、特に指定しない場合、「107c4fc35748ce」のような形で、14桁の英数字小文字が生成されていると思うのですが、これはどうやって生成しているのでしょうか?(アルファベットがfまでになっているようなので、16進数のhash値なのかな?とは思っているのですが、14桁というのがピンと来ませんでした)

syotarosyotaro

idのように、既存のユニークなものを変換しているのかなと思ったのですが、単純にランダムな値を生成(その後、既存のslugと被っていないかチェック?)して、生成しているのですね。スッキリしました。ありがとうございます。

KeisukeNagakawaKeisukeNagakawa

トップページはSSR(Server Side Rendering)とありますが、それにしても描画速度がはやくてすごいなとおもってます。なにか工夫はさていらっしゃいますか?

MelodyclueMelodyclue

本の作成についてです。
チャプターの並び替えをどのようにDBで保存しているか気になっています。

見た感じ、本の保存時に、チャプターIDの配列をAPIに渡して、そのあと再取得してきたレスポンスは新しい並び順になっていました。

チャプターIDの配列をバックエンドでどのように扱って順番を入れ替えているかお聞きしたいです。

このスクラップは2022/05/01にクローズされました