uiu です。
前回の記事 『なぜ Next.js をやめたのか?』 では、Reactベースのウェブフロントエンドで、サーバーサイドレンダリング(SSR)をしない選択をすることで、アーキテクチャをシンプルに保っているという話を紹介しました。
アーキテクチャをシンプルに保つ工夫のおかげで、ウェブとアプリのコード共通化が簡単になり、1人のエンジニアが最小限の労力で複数プラットフォームに変更を入れることが可能になっています。
Dynamic Rendering はスケールしないのでは?メンテが大変なんでは? という質問があったので、今回は実際どのように Dynamic Rendering を運用しているかについて紹介したいと思います。
結論を簡単に言うと、キャッシュが効くためサーバーコストは高くなく、またサーバーレス環境で動いてるため運用のメンテナンスコストも低いです。
サーバーコストは当然 SSR での運用と比較すると高いものの、全体のコストと比較すると微々たるものです。そのため、開発生産性を優先して Dynamic Rendering する選択をしています。
Dynamic Rendering とは
Dynamic Rendering は、検索エンジン最適化(SEO)を目的に、クローラーのリクエストのときのみ Headless Chrome を使い、JSを解釈することで、レンダリング済みの HTML をレスポンスとして返すテクニックです。
検索エンジンのクローラーかどうかは、サーバー側で User Agent を見て分岐します。
一般のユーザーからのリクエストに対しては、通常通りレンダリングせずにレスポンスを返す、というのがポイントです。
怪しい裏技的なテクニックではなく、Google の公式でも詳細に説明されている方法です: Dynamic Rendering | Google Search Central | Documentation | Google Developers
技術要件
あらゆる問題に対して最適な解法がないのと同じように、技術要件なしに技術選択を語ることはできません。
弊社サービスでの技術要件は以下のようなものでした:
- 検索エンジン最適化と各種SNSでのOGP展開等が目的
- ページ数は高々100万ページ程度
- サービス性質的に、ページの更新性はそれほど高くない (1日1回更新できれば十分)
YAGNI
当然、将来的に、インデックスするページ数を多くしたい、ページの更新性を高くしたい、などといった要求仕様の変更が起こる可能性はあります。
しかし、それは不確実な未来の低い可能性です。現時点では将来的な要件は重視しない判断をしています。
現時点で、精度高く必要だと言える要件に集中することで、投資のコストとリターンを判断し、最適な技術を選択しています。
リソースの限られたスタートアップでは、過剰な投資を避け、無駄を省き、他のプレイヤーが時間を浪費している間に速く動くことが大事だと考えています。
一言でいえば、Keep It Simple もしくは YAGNI になります。
クライアントサイドレンダリングを選択しなかった理由
最近のクローラーは JavaScript を解釈するから、クライアントサイドレンダリングでいいのでは? という指摘もあるとは思います。
以下の理由で Dynamic Rendering をしています: - デバッグがしやすい: クローラーによる JavaScript 解釈の挙動が予測しづらいため、自社サーバー側で静的HTMLにしておくほうが動作検証がしやすい - Google以外の検索エンジンへの対応 - Twitter, Facebook, Slack など各種SNSでのOGP展開をしたい
サービスの要件次第では、クライアントサイドレンダリングを選択することは可能だと思います。
ただし、問題が発生した際に速やかに対策したい場合を考えると、動作を再現しやすい環境にしておくのは悪い投資ではないです。
運用
Google がOSSで公開している Dynamic Rendering サーバー実装である rendertron を利用しています。
GCP の Cloud Run 上で運用しており、サーバーレス環境でオートスケールを活用して動かしています。メンテナンスに関して困ったことはあまりなく、2年以上ほぼノーメンテで動いています。
当初は Google App Engine で rendertron を運用していましたが、サーバーコストを抑えるため Cloud Run に移行しました。 *1
設定
基本的な設定に関しては、Dockerfile を用意して、キャッシュ設定やタイムアウト設定を追加した程度で、比較的簡単にデプロイできました。
Vercel の Rewrite 機能を使って、User Agent で分岐してクローラーからのリクエストを振り分ける挙動を実現しています。
他にも nginx 等リバースプロキシを使う方法や Cloudflare Workers を使う方法などが考えられると思います。
具体的な設定については Vercel を導入した際の記事で説明しています:
レイテンシー
平均や95%パーセンタイルでのレイテンシーの数値は高いですが、レイテンシーが原因で悪影響は今のところ出ていません。
運用して長いですが、検索トラフィックは堅調に伸びています。検索エンジンは事業的にも重要な流入経路の1つであるため、レイテンシーにより悪影響があるかは入念に検証しましたが、今のところ大きな問題は出ていなさそうです。
rendertron では Cloud Datastore ベースのキャッシュが動いており、同一ページへのリクエストに対してはレスポンスを高速に返すことができます。運用しているサービスの技術要件では、更新性がそれほど高くないため、キャッシュを活用しやすいです。
ユーザー体験についてもOGP展開等ではキャッシュヒット率が高く、大きな問題はないと思っています。
インフラコスト
クラウドでのサーバーコストの観点でいうと、全体のインフラコストの比率から言っても、割合は少なく許容範囲です。
キャッシュが活用できるため、トラフィックに比例してサーバーコストが増加するわけではなく、将来的にもサーバーコストは一定範囲に収まると予想しています。
開発人件費に比べると微々たるものであり、開発生産性を犠牲にしてまでサーバーコストをケチる判断をする理由にはなりません。
他社事例
導入時には知りませんでしたが、より大規模で更新性の高いサービスでも Dynamic Rendering の導入事例があるようです。
まとめ
Dynamic Rendering はスケールしないのでは? という懸念は、チーム内で当初導入を検討していたときにもありました。
入念に調査をし小さく技術検証を行うことで、この懸念は大きな問題でないことが検証できたため、Dynamic Rendering を導入する判断をしました。
フロント側コードでサーバー環境での動作を考える必要がなくなり、フロントエンドの開発生産性を高く保つことに貢献しています。
当然将来的には状況に合わせて、変わる可能性はありますが、現時点ではこれがより最適な技術選択であると思っています。
ハローでは、単に流行りを追うのではなく自分で考え技術選択ができるエンジニアを募集しています。
少しでも気になる方は気軽に uiu までDMでお声がけください!
*1:当時、同等スペックのインスタンスが App Engine よりも安かった