Hello Tech

AutoReserve 等を開発する株式会社ハローのテックブログです。スタートアップの最前線から本質的な価値を届けるための技術を紹介します。

脱SSRのための Dynamic Rendering 運用

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 を導入した際の記事で説明しています:

tech.hello.ai

レイテンシー

平均や95%パーセンタイルでのレイテンシーの数値は高いですが、レイテンシーが原因で悪影響は今のところ出ていません。

運用して長いですが、検索トラフィックは堅調に伸びています。検索エンジンは事業的にも重要な流入経路の1つであるため、レイテンシーにより悪影響があるかは入念に検証しましたが、今のところ大きな問題は出ていなさそうです。

rendertron では Cloud Datastore ベースのキャッシュが動いており、同一ページへのリクエストに対してはレスポンスを高速に返すことができます。運用しているサービスの技術要件では、更新性がそれほど高くないため、キャッシュを活用しやすいです。
ユーザー体験についてもOGP展開等ではキャッシュヒット率が高く、大きな問題はないと思っています。

インフラコスト

クラウドでのサーバーコストの観点でいうと、全体のインフラコストの比率から言っても、割合は少なく許容範囲です。
キャッシュが活用できるため、トラフィックに比例してサーバーコストが増加するわけではなく、将来的にもサーバーコストは一定範囲に収まると予想しています。

開発人件費に比べると微々たるものであり、開発生産性を犠牲にしてまでサーバーコストをケチる判断をする理由にはなりません。

他社事例

導入時には知りませんでしたが、より大規模で更新性の高いサービスでも Dynamic Rendering の導入事例があるようです。

inside.pixiv.blog

まとめ

Dynamic Rendering はスケールしないのでは? という懸念は、チーム内で当初導入を検討していたときにもありました。
入念に調査をし小さく技術検証を行うことで、この懸念は大きな問題でないことが検証できたため、Dynamic Rendering を導入する判断をしました。

フロント側コードでサーバー環境での動作を考える必要がなくなり、フロントエンドの開発生産性を高く保つことに貢献しています。
当然将来的には状況に合わせて、変わる可能性はありますが、現時点ではこれがより最適な技術選択であると思っています。

ハローでは、単に流行りを追うのではなく自分で考え技術選択ができるエンジニアを募集しています。

少しでも気になる方は気軽に uiu までDMでお声がけください!

hello.ai

*1:当時、同等スペックのインスタンスが App Engine よりも安かった

なぜNext.jsをやめたのか?

javascripterです。ハローでは、プロダクトのローンチ前からAutoReserve の開発に関わっています。

この記事では、AutoReserveウェブ版が、Next.jsを一度採用したがやめ、その後create-react-app + react-routerの構成に移行した経緯を書きます。

ウェブ版開発の背景

AutoReserve はAIが電話予約を代行してくれる飲食店向け予約グルメアプリで、現在はiOS / Android / ウェブにサービスを展開しています。 元々はReact Native製のネイティブアプリのみ展開していましたが、ユーザ獲得の面でウェブ版が必要となったため、 追加でウェブ版を実装し、現在の3プラットフォームでの展開に至ります。

最初の技術選定

ウェブ版の最初のバージョンでは、フレームワークとしてNext.jsを採用しました。Reactで書け、SEOのための最適化やサーバーサイドレンダリングが行えるためです。

理由をいくつか挙げると、

  • AutoReserveは全国のレストラン情報を掲載するメディアサイトなため、SEOを考慮する必要がある
  • 先行してローンチしたReact Native製のアプリと技術スタックをなるべく揃えたい

といったものがあります。

また当時、AutoReserveアプリで採用していた技術として、

  • React Native
  • redux / redux-saga

があげられます。よって、ウェブ版でも同様にredux, redux-sagaを使用することにしました。

React Native for Webの採用

ウェブ版を開発するに当たって、初期はVercelの styled-jsxをCSS in JSとして利用し、プレーンなReactアプリとして実装していました。

しかし、下記のような課題が生じていました。

  • アプリ版とウェブ版で二重にコンポネントを実装しメンテナンスしていくのが非効率的
  • styled-jsxではCSSセレクタを自由に使えるが、CSSセレクタには詳細度などの概念があり、最終的にどのスタイルが適用されるのかぱっと見でわからず複雑

これらの問題を解決するため、styled-jsxを使用するのはやめ、React Native for Webという、React NativeのAPIやコンポネントをウェブ向けに実装したライブラリを使用し、React Nativeのコードをそのまま移植する方針を取りました。

React Native / React Native for Webでコードを書く場合はこのようになります。

function Component() {
  return (
    <View
      style={{
        flex: 1,
        backgroundColor: 'red',
      }}
    >
      <Text>Hello</Text>
    </View>
  )
}

React Nativeのスタイリングの仕組みの特徴としては

  • Viewのスタイルに元々display: flexが当たっている
  • メディアクエリや::before::afterなどには対応していない
    • 動的なスタイルの変更はJSによって行う
  • Atomic CSSを採用しているため、CSSのカスケーディングが存在しない(ウェブ)

ということが挙げられます。

発生した課題

AutoReserveのアプリ、ウェブ版の大きな違いとして

  • アプリ版はスマホ向けレイアウトオンリー
  • ウェブ版はスマホ、PC向けのレイアウトの両方が必要

ということがあります。

React Native for Web自体はメディアクエリに対応していないため、レスポンシブに実装する必要がある場合、divをコンテナとして囲って、そこにスタイルを当てる必要がありました。

また、オートリザーブでは、単純なレスポンシブデザインではなく、PCとモバイル向けで、完全にわかれたコンポネントを出し分けたい場面が多くありました。

具体的には、

  • PCの場合は予約のフォームをインラインで出したい
  • モバイルの場合は予約ボタンのみを表示し、タップしたらハーフモーダルを出したい

といった場面です。こういった場合、@artsy/fresnelなどのライブラリを使い、PC・モバイル両方のコンポネントをレンダリングしメディアクエリで片方のコンポネントを隠す必要がありました。

その他、対応が必要だった点を挙げると、

  • redux-sagaでロード完了状態を待ってSSRした結果を返す必要がある
  • ログインが必要なページはキャッシュを行わないようにする
  • reduxのstateをサーバーサイドとクライアントサイドで共有する
  • cookieに認証情報を同期する

などが挙げられます。一つ一つの問題は解決可能でしたが、全体として見ると複雑度が高まり開発効率が低下する原因となっていました。

SSRをしないという選択

なぜNext.jsでうまくいかなかったか

  • SSRを前提としていない設計のアプリのコードをウェブに移植した

というのが移植の上で発生した問題の主な要因かなと思います。

SPAへの移行

そこで、思い切って元々のアプリの設計に寄せるため、サーバーサイドレンダリングを行わず、SPA(Single Page Application)として書き換えることにしました。

SSRについては、Dynamic Renderingを行ってみることにしました。

Dynamic Renderingとは、JavaScriptを解釈しないクローラのために、クローラがページにアクセスした際に裏でヘッドレスのブラウザを立ち上げJavaScript実行後のHTMLを返す、という技術です。AutoReserveのウェブサイトではRendertronを使用しています。

オートリザーブでは、クローラからのアクセスがあった場合、モバイル版のサイトをDynamic Renderingし返すように実装しています。

Dynamic Rendering の具体的な運用については以下で詳しく説明しています: tech.hello.ai

サーバーサイドレンダリングを行わずSPAになったため、技術スタックは下記のようになりました。

  • react-router
  • react-native-web
  • redux/redux-saga(現在はreduxではなくswrを使用)

どうなったか

実行環境がブラウザのみになったため、Node.jsで実行した際の動作環境の差異を考慮する必要がなくなり、開発スピードが上がり不具合の発生頻度も減りました。

レスポンシブ対応についても、JSの分岐によりレスポンシブ対応を行えるようになり、コンポネントを切り替えたり、プロパティの値を分岐したりといった柔軟な対応が行えるようになりました。 具体的には、このようなコードで書けるようになりました。

function Component() {
  const { width, sm, md } = useResponsive()

  if (width < sm) {
    return <SPComponent />
  }

  return <PCComponent style={{ paddingHorizontal: width < md ? 16 : 24 }} />
}

現在に至るまで、サイトのトラフィックの増加は順調で、SEO上の問題は生じていません。

また、サーバーサイドレンダリングを廃止し、React Native for Webを使用するようになったため、ウェブとアプリのコード差異がほとんどなくなりました。 結果的に、ウェブのコードの大部分をアプリからのコピペで実装できるようになり、現在ではar_sharedという共通パッケージを作成し、ウェブとアプリでコードの共通化をおこなっています。

現在、アプリとウェブでコード共有できている部分は

  • ボタン、チェックボックスなどのUIコンポネント
  • 色の定義などの定数
  • Reactのhooksや日付・数値のフォーマット、文字列操作などのユーティリティ関数
  • TypeScriptの型
  • 予約の表示画面、予約送信画面のコンポネントなど、アプリケーション画面のコード

です。ウェブとアプリでコードを共通化する取り組みについては、今後このブログで別記事で詳しく取り上げる予定です。 アプリとウェブの画面遷移やルーティングの仕組みの違いなどプラットフォームの差異を吸収するため設計上工夫が必要な面もあり、そのあたりについても解説していく予定です。

まとめ

サーバーサイドレンダリングをしない、という選択によって開発を単純化し、少人数で多プラットフォームに同時展開できるようになったという事例を紹介しました。

社内ではNext.jsを使用しているプロジェクトもあり、プロジェクトごとに最適な技術選択を行い生産性を追求しています。

ハローでは、開発スピードを追求する本物のエンジニアを募集しています。

少しでも気になる方は気軽に javascripter にDMでお声がけください!

エンジニア採用情報 | Hello, Inc.

スタートアップがWebマーケティング戦略を考える前にやるべきこと

はじめまして!

株式会社ハロー、マーケティングチームです。日頃は、サービス全体の設計やAutoReserveの問い合わせ数増加施策などグロース全般を担当しています。 4月は新社会人や転職してこれからマーケターとしてやっていくという方がいると思います、そういう方に向けて「Webマーケティングって何をすればいいの?」という質問に自分なりに回答をしてみたいと思います。合わせて各トピックにおいて参考となりそうな本についても紹介します。

Webマーケティング戦略を考える前に大事にしたいこと

f:id:hello-tech:20220406122401p:plain

いきなり残酷な話ですが世の中のほとんどのサービスは、思った通りには伸びません。

これを読んでいる方も 「こういうサービスを作ったら絶対売れる」 「○○の悩みを解決したら確実に成功する」 と思ったことはないでしょうか?

私も同じように、幾度となく同じようなことを考えたことがあります。例えば、「寝坊する人が多いから、絶対寝坊しないためにモーニングコールのサービスを作ったら売れるはずだ」とか....いろんな妄想をして人生を過ごしてきました。

Webマーケティングってなんかいい響きだし、うまくできる人ならなんでも伸ばせるんだと思っている人がいるならそれは間違いです!

「Webマーケティング戦略を考える前に大事にしたいこと」というのは、「そもそも市場があるところで勝負して初めてWebマーケティングが生きてくる」ということです。確かに、市場にない製品を生み出してうまくいく、iPhoneのようなものもあると思います。ただ、市場がないところから商品を理解し、売ってもらうためには多額の資金を用意してマーケットを自ら作りに行って初めてその商品が売れるかわかるため、成功確率も基本的には低いとされています。

なにより、みなさんが日頃触れている製品やサービスも革新的なものというより、基本的にはすでに知っているものがリプレイス(代替すること)されているだけのものが多いと思います。それくらい市場にないものに理解を得ることは難しいということです。今手元に持っているもの、見渡しているもの、使っているものもほとんどが何かに代替されていると思います。

何をまずチェックすべきか

結論、Webマーケティングを行うためにはこのように ・なにをリプレイスするものなのか ・どれくらい市場が存在するものなのか を見極めて初めて戦略を考えることができるようになります。闇雲にいろいろ試しても意味はありません。これから本格的にWebマーケティングを行おうと思っている方はぜひ要素分解からするようにしてみてください。

リサーチにおすすめの書籍は四季報

f:id:hello-tech:20220406121652p:plain

いろいろな企業がどういう商品を代替して、どういう市場で戦っているかをたくさん見ることで事業リサーチ能力が鍛えられます。ぜひ読んだことがない方は四季報を読んで、企業の事例をたくさん読みあさってみてください。

※四季報とは ー 1936年に創刊。上場企業を中心にしたデータを網羅している会社専門誌。四季報記者たちの専門的取材があるため、あくまで中立な立場で業界分析を行っている書籍です。

事業領域と競合分析が終わったら、次はWebでどうやってお客様がくるか考えてみよう

先ほど、「リサーチが大事である」とお伝えしました。

では、実際にWebマーケティングを行う上でこのリサーチがどう役立ってくるのかについてお話しします。

例えば、今回オンラインで「コップ」を売ることにしましょう!(今回オフラインについては無視して記載します)

まずダメな事例は、すぐに「SEOをやろう」「広告をやろう」「インスタで売ろう」と戦略を決めずに手を動かすことです。もし仮にうまく行っても戦略をすっ飛ばしてしまうと再現性がなく、また失敗した時も失敗から学ぶことができません。

コップに接点を持つタイミングを考えよう

コップにユーザーが興味を示したり、購入したりする接点はオンラインにどれくらいありそうでしょうか?Amazonや楽天など多数のショッピングサイトから購入されることは誰でもわかるところですが、例えばamazonで今調べると、

f:id:hello-tech:20220406121738p:plain

60000件以上のコップが存在します。つまり最初にお伝えした市場というのは間違いなく合いそうというのはわかります。ただ、競合が多すぎてリプレイスすることの難易度が高そうです。ただ購入者の母数も大きそうなので、始めるタイミングというのはいつになるかわからないですが、いつかは必ず参入しないといけないということだけはわかります。この時にに初めて数値データを元に、戦略の1つが決まるわけです。

ただ、いきなり60000種類ある市場に作ったコップを出してもだれも知らないブランドなのできっと購入しません。では、それ以外にコップに接点を持つタイミングや抜け道を考えてみましょう。

例えば、

  • お祝いの時に贈る
  • 一人暮らしを始める時に買う
  • 同棲をする時に買う

など様々なケースが考えられます。

さらに、その接点はどこから起こり得るか考えてみます。

  • お祝いの時に贈る → お祝い専門サイトで買うかも・・・
  • 一人暮らしを始める時に買う → 「引越し 買うべきもの」と引越しについてGoogleで調べているユーザーが接点を持った上でamazonで購入しているかも・・・
  • 結婚をする時に買う → 「結婚」についてGoogleで調べている時に買うかも・・・

と、接点がなんとなく考えられると思います。思いつかない時は対象ユーザーになりそうな方にヒアリングを行うこともおすすめです。

接点を深堀しよう

上記の接点をもう少し調べてみます。

お祝いの時に贈る f:id:hello-tech:20220406121945p:plain 引用:https://giftmall.co.jp/search/?q=%E3%82%B3%E3%83%83%E3%83%97

ギフト専門サイトでコップと検索すると、2000件ほどのアイテムが確認できました。amazonよりは少ないので、少しチャンスがあるかもしれません。

一人暮らしを始める時に買う f:id:hello-tech:20220406122006p:plain 引用:https://hikkoshizamurai.jp/price/household-goods/

引っ越しのときに買うべきものにはコップがほとんどのサイトで紹介されていました。たとえば、このメディアに連絡をして紹介をしてもらえばもしかすると売れるかもしれません。

結婚をする時に買う f:id:hello-tech:20220406122025p:plain 結婚 ペアグラス でamazonで検索しました。そうすると一気に9000件まで減っていることがわかります。例えば、他社が取り扱っていない材質や色で作ればもしかすると売れるかもしれないという可能性がわかります。

ざっくりで終えない、さらにここから無料でできるリサーチを徹底的に行う

Webマーケティングの醍醐味は「数値で会話できる」ということです。例えばamazonならamazon広告を利用すれば1人あたりいくらでお客様を自分の商品ページに誘導できるか、検索されている数はどれくらいか把握可能です。

またGoogleも同じように検索ボリュームがわかります。さきほど説明した媒体と連携して商品を掲載するか相談できる場があれば対象のURLのアクセス数を事前に聞けるかもしれません。

無料でできることを徹底的に行えばWebマーケティングはある程度の数値を算出でき、また数値の効果がいい・費用対効果がいいものを調べることができます。

「リサーチをする→数値で議論できるデータを集める→数値を元に施策を行うか行わないかを判断する」、このサイクルを効率よく回せるように株式会社ハローでは日々社内のチームメンバーでも数値をエビデンスに議論することを徹底しています。

Webマーケティングでよく使われるチャネル(集客経路)について

f:id:hello-tech:20220406122508p:plain

さて、リサーチについてこれ以上書くと本題と脱線しそうなためコップの例を元に接点の理解についてはある程度学んでいただけたと思います。あとは接点がGoogleでの検索なのか、はたまたインフルエンサーが紹介しているから売れるのかなどを調べ、それを元に効果のありそうな順番でWebマーケティングの戦略を構築してみてください。

Webマーケティングでは言葉の通り、オフラインではなくWeb上で行うマーケティング手法のことです。最近では以下のものをよく使うため箇条書きにしてまとめました。(それぞれの広告について、学べる本についてはリンクでご紹介しました)

・SEO

・SNS運用 ・リスティング広告 ・ディスプレイ広告(本はリスティング広告の本で説明がある場合が多いです)

・アフィリエイト広告

・メルマガ

・媒体掲載 ・SNS広告(本については広告というよりマーケティング全般となります、詳しい運用手法はインターネットにたくさん落ちています)

・インフルエンサーマーケティング

インパクト(施策効果)があるWebマーケティングの発想について

Webマーケティングはインパクトのある施策をたくさん成功させればさせるほどサービスが伸びます。

インパクトのある施策を考える上で大事になるのは、「競合をとにかく分析する」ということです。競合は今まで説明してきた接点をきっとあなたと同じかそれ以上考えて成功させています。つまり、相手がどの媒体でどうやってお客様を集めているかがなんとなくわかれば同じような施策を打つことで「相手が失敗してきた広告費を同じように使うことなく、成果を出すことができる確率が上がる」と言えます。

わかりやすくいうと、例えば競合が伸びていると聞くけど全くWeb広告を見ない、サイトにも掲載されていないという場面で考えられるのは自分に接点のない媒体でのユーザー獲得になります。そうすると、「メルマガ広告」や「紹介営業(オフライン)」などが考えられます。顧客層を抱えていそうなメルマガに連絡を取り、競合が出稿しているか確認することが見つけられるかもしれません。

Web広告は打っていそうだが、どういう広告を使っているかわからないときにはツールを使うのも1つの手です。Facebook広告、他社の広告をチェックできるツールが存在します。競合名で調べるといつから配信しているかも調べることができるかもしれません。

Webマーケティングでは競合の状況を見て、いいものは真似をするというのも大事な1つの戦略です。インパクトがいまいち出ていない時には気にかけるようにしてください。

まとめ:Webマーケティングは全ての人にチャンスがある

このブログ1記事では語ることができませんでしたが、ハローでは長期的に効果が出る・費用対効果の高い施策を中心に取り組んでいます。

ぜひこれからWebマーケティングを極めていきたい・Webがよくわからないが効率的な戦略を打ち出していきたいという方は参考にしていただけると幸いです。

また、「こういう議論のできるチームで働いてみたい!」と思ったそこのアナタ、ハローでは常にメンバーを募集しています!

ぜひこれをみていただいているのも何かのご縁ですので少しでも気になった方は気軽にご応募ください!

採用情報 | Hello, Inc.

Vercel の monorepo にWebフロントすべてを移行した

uiu です。ハローには創業時に入社し、エンジニアとしてAutoReserveの開発にゼロから関わってきました。現在はバックエンドをメインに担当していますが、領域横断的に開発することを得意としています。

2022年の初めに AutoReserve にあるWebフロントエンドをすべて Vercel に移行しました
Vercel に移行するのと同時に Turborepo を導入しました。現在、4サービスのWebフロントエンドを monorepo として運用しています。

AutoReserve は、AIが代わりに電話してくれる飲食店向け予約グルメアプリです。iOS / Android アプリ、 Web アプリを提供しています。
また、セルフオーダーシステム AutoReserve Order を提供しており、レストランすべての業務をサポートできるプラットフォームを目指しています。

背景

AutoReserve では React / React Native を活用し、iOS / Android / Web のプラットフォームに同時展開をしています。

iOS / Androidネイティブアプリは React Native で開発し、Web では react-native-web を活用しネイティブ側と同じ構造のコードで開発できるようにしています。
フロントエンドを1つの言語で開発し、最大限コードを共有することで、小さなチームでスピード感のある開発をできるようにしています。

Web には管理画面含め合計4つのサービスがあり、すべて React で開発されています。

1つ特徴を挙げると、Next.js を使わず Server-Side Rendering をしていないということです。代わりに Dynamic Rendering を使って、検索エンジン対策をしています。

SSRを利用しないことで、クライアントサイドでの動作のみを考えればよくなります。そのおかげで、アーキテクチャをシンプルに保つことができ、React Native側とコードが相互に再利用しやすくなっています。

簡単にキーワードを並べると以下のような技術が使われています:

  • TypeScript
  • React Native
  • react-native-web
  • redux, redux-saga
  • swr
  • Cypress

導入前の課題

Vercel 導入前には、Google Cloud 上のサーバーで自前でビルド環境・配信環境を整備して運用していました。 しかし、運用するサービスの規模が大きくなるにつれて、以下のような課題がでてきました。

デプロイの設定・運用コストが高い

デプロイ時に、GCPインスタンス上で yarn install && webpack build をして nginx 経由で配信する仕組みになっていましたが、
ビルド時間がボトルネックになりやすく、リリース時に待ち時間が大きく発生していました。

ビルド時間は遅いときで約8~10分かかっていました。

ビルド時間を快適に保つためには、CPUリソースを適切に確保したり、適切にキャッシュ設定をしたりする必要があります。そのために、事あるごとにメンテナンスをする必要があり、運用コストが高い状況でした。

また、新しいサービスが増えるごとに、新たに nginx 等で設定をする必要があり、面倒でした。

コード共有が難しい

ネイティブとWebで共通する component や logic を共通ライブラリ(ar_shared)として切り出しています。
個々のWebアプリを別リポジトリとして運用して、個別リポジトリからは ar_shared を package として参照していました。
しかし、実際に運用してみると、リポジトリをまたぐ変更がしづらく課題を感じていました。

手元で yarn link する、開発時に共通ライブラリのアップデート時に GitHub Actions で version bump commit をする、などのテクニックが必要でした。
また、lintやCI設定など、リポジトリをまたいで共通化したい設定があっても、個々のリポジトリで設定しなければなりませんでした。

このような理由で、複数アプリケーションを monorepo 化する機運が高まっていました。

Vercel 導入を決めた理由

Vercel 導入を決めたのは以下のような理由でした:

  • ビルドが速い
  • Dynamic Rendering が可能
  • monorepo をサポートしている

ビルドが速い

一番驚いたのは、特に設定をせずともビルドが速いことでした。
試したリポジトリではデプロイまでにかかる時間が 約8分 -> 約3分 になりました。

ビルドコンテナの立ち上がり時間等が速く、 Cloudflare Pages 等と比較しても圧倒的に速かった。

Dynamic Rendering が可能

Dynamic Rendering では、クローラーの User-Agent を区別し、クローラーに対しては headless chrome でレンダリング済みのHTMLを返します。 リバースプロキシ等を使い、配信の前段で User-Agent を元に出し分けをする必要があります。

Vercel では Rewrites を使うことでこの挙動に対応できます。

monorepo をサポートしている

monorepo をサポートしており、Turborepo を使ったビルド高速化の恩恵を受けられることがメリットでした。

Vercel の公式ドキュメントでも monorepo サポートが公言されていて、今後の機能拡張が期待できます。

vercel.com

他の選択肢

他のホスティングも検討しましたが、選択しなかった当時の理由は以下です:

導入時の工夫

Dynamic Rendering の設定

rewrites の設定を書きます。検索エンジン最適化の重要性が高いので、動作検証を検証環境でしてからドメインの切り替えを実行しました。

rendertron という headless chrome でHTMLを描画してHTMLを返すサービスに proxy してます。

vercel.json に以下のように設定を書けます:

{
  "rewrites": [
    {
      "source": "/:match*",
      "destination": "https://rendertron-host/render/https://autoreserve.com/:match*",
      "has": [
        {
          "type": "header",
          "key": "user-agent",
          "value": ".*(?:Googlebot|Bingbot|Yandex|Y!J|Baiduspider|Twitterbot|facebookexternalhit|rogerbot|LinkedInBot|Embedly|quora link preview|Pinterest|Slackbot|vkShare|W3C_Validator|Linespider).*"
        }
      ]
    }
  ]
}

curl でリクエストを飛ばすことでHTMLがレンダリングされているかどうかを確認できます:

curl -A "Googlebot" "https://your-app.vercel.app/restaurants/xxxxxxx"

sitemap 等を同一ホストパスで配信している場合は、それも設定しなければいけません。リリース時にこの設定を見落としていて、 sitemap が配信されない問題が発生して困りました。

{
  "rewrites": [
    {
      "source": "/sitemap.xml",
      "destination": "https://.../sitemap.xml"
    }
  ]
}

monorepo への統合

git の履歴を保ちつつ、複数リポジトリを monorepo に統合するのには、 subtree merges と filter-repo を使いました。

https://docs.github.com/en/get-started/using-git/about-git-subtree-merges

https://docs.github.com/en/get-started/using-git/splitting-a-subfolder-out-into-a-new-repository

時間がかかったのは、turborepo の設定です。
turborepo を使うには yarn workspaces を設定する必要がありますが、yarn workspaces での依存解決は同じyarnだとは思えないほど複雑で、ビルドを通すまでにかなり時間をとられました。

大量の package を nohoist するなどの方法を使ってビルドが通るようにしました。最終的には、nohoistをやめ、アプリそれぞれの依存パッケージのバージョンを気合で揃えることで統合しました。

導入後の効果

狙っていた効果として、ビルドが速くなること、運用コストを減らすことが達成されたので満足しています。
また、monorepo化によって開発環境のセットアップが簡単になり、webフロント開発の変更はしやすくなったのを感じています。

featureブランチごとにデプロイする機能(Preview)が使えるようになり、変更の動作確認が簡単になったこともかなり嬉しいポイントです。

目玉機能の1つである global edge network での配信については、現状特にメリットを感じていないというのが正直なところです。
主要ユーザーが日本の場合、例えば東京にnginxサーバーを単に置くのと、スピード面では少なくとも体感差が出ないと思います。

導入後の課題

Vercel 導入後に現状感じている課題はいくつかあります。

料金がやや高い

コミッター数で課金される、並列ビルドで課金される、など課金ポイントが多く、料金が高くなりがちです。
業務委託等で関わってくれているメンバー数が多いため、開発者が1人増えるごとに課金が高まっていきます。

Pricing – Vercel

ビルドが詰まりがち

monorepo の変更内容によっては、すべてのアプリでビルドが trigger されることになります。
開発するタイミングが重なると、ビルドキューが詰まり、待ち時間が発生することがよくあります。

並列ビルドも課金対象であり($50/月)、たくさん並列数を用意しておくのには限界があります。

Ignored Build Step により、diffを見て必要なアプリだけビルドすることは可能ですが、結局共通ライブラリへの変更時にはあまり効果がありません。

リリース管理がむずかしい

複数アプリのリリースを monorepo でどうやって管理すべきか、についてはまだ良い解決策が見つかってません。

アプリによってリリースサイクルが違ったり、アプリ個別にリリースを制御したいケースがあるため、個別にリリースブランチを用意しています。

現状では、master と production-app-1, production-app-2, ... のようにリリースブランチを用意しています。
しかし、4コのアプリがあるため、1つ1つリリース作業をするのが面倒でリリースするのを忘れる、などの問題があります。

まとめ

Vercel で monorepo 開発運用をしている事例を紹介しました。
非 Next.js アプリでも Vercel に載せるメリットは十分にあることを紹介できたのではないかと思っています。

今後は、React Native のネイティブアプリを monorepo 統合するなど、少人数で大きな開発ができる仕組みを一層整えていく予定です。

ハローでは、生産性を追求する本物のエンジニアを募集しています。

少しでも気になる方は気軽に uiu にDMでお声がけください!

エンジニア採用情報 | Hello, Inc.