Open105

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

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

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

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

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

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

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

スクラップについてまだ周知していない段階で見つけていただきありがとうございます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になっている項目のみ)一括で送るようにしています。

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

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

バックエンドには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を活用するための一つの指針となりそうな解決法ですね。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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のような形で表現しています。

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

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

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

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

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など、、、

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

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

などですね。

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

なるほどー
ありがとうございます。

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

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

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

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

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

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

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

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

なるほどです...!!
お忙しいところありがとうございます。勉強になります。

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

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

よろしくお願いします!

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

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

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

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

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

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

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

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

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

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

よろしくお願いします。

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

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

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

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

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つの差はなんなのか気になりました!

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

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

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

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

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

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

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

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

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

  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の国内事例を殆どしらないので楽しみにしてます!

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

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

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

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

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

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

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

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

ありがとうございます!

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

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

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

そうだったのですね。

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

実は外出しも出来るようで、その問題は解消できるかもです。

https://github.com/vercel/styled-jsx#external-styles

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

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

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

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

よく分かりました。ありがとうございます。

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

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

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

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

という感じです。

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

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

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

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

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

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

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

こんにちは、RailsのAPIについて質問させてください。

https://zenn.dev/catnose99/articles/zenn-dev-stack#rails(apiモード)

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

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

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

そうなんですね。ありがとうございます!

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

やっぱりそうですよね。

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

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

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

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

了解しました。ありがとうございます。

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

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

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

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

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

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

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

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

以前別の方の質問に

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

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

あまり詳しくは書けないのですが、銀行のオンライン振込を独自の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

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

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

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

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

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

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

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

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

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

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

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

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

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

railsでjsonのserializerって何か使ってますか?

Hidden comment

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

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

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

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