オウチーノ開発者ブログ

「株式会社オウチーノ」の社員によるブログです。

Rails+ReactなSPAサイトでSEOをしようとしてぶつかった壁

Rejectcon 2018 レポート記事

こんにちは。オウチーノ吉川です。

去る9/29にRejectconで「Rails+ReactなSPAサイトでのSEO」について登壇してきました。 SPAサイトでSEOを行う際にぶつかった壁と、それをどのように乗り越えていったかの経緯をご紹介しました。

ちょっと内容詰め込みすぎて途中途中端折ってしまったのと、発表後にもいろいろ質問などをいただいていたのであわせて補足しながらご紹介します。

発表資料はこちらです。

なぜ経緯を紹介したのか?

昨今のフロントエンド技術の進化速度は非常に速く、どんどん新しい技術が登場しています。 記事などでどういった技術が登場しているのかは抑えているが、実際に全てちゃんと触ってはいないという方も多いのではないでしょうか。

SSRをするためにはreact-railsやhypernovaがある、ということを知っているだけでは、いざ使うとなったときにどちらを導入すべきか判断できません。 Dynamic Importsについても、利用ケースを知らなければなぜこんな面倒なことをするのか、という印象を持つかもしれません。

今回の対応を進めるにあたって、対応を進めるに連れて昔読んだ記事にたどり着いて、最初からヒント読んでたのに!となったことが何度もありました。 そのため背景をお伝えすることが重要だと考えました。

React-HeadによるSSR

時間がなくて端折ったところです。

React-HeadはSSR対応head管理ツールです。コンポーネントとしてmetaタグなどを設定しておけば、head部分に適切にタグを挿入してくれます。 と、CSRの場合はそれで良いのですが、SSRの場合は全体のレイアウトを別途持っているはずで、その適切な場所に入れ込む必要があります。

React-Headのアプローチは、HeadProvider にArrayを渡しておけば、コンポーネントとしてセットされたmetaタグなどを集めてArrayに入れておいてくれます。そのArrayのみを別途renderToString して差し込んでやるというアプローチです。 これ自体もなかなか複雑なアプローチですが、あくまでNode.jsを想定したアプローチで、hypernovaの場合はそのmetaタグコンポーネントを保持しているのがhypernova、テンプレートはRails側にあるためプロセスをまたいで渡す必要が出てきます。 そこでhypernovaがレンダリングしたルートコンポーネント部分のhtmlにカスタムタグとしてくっつけてRailsに返し、Rails側で文字列操作で適切な場所に入れ替えるという力技で対応しています。

結局SSRはSEOのために必要だったの?

比較検証ができていないので断言はできませんが、SEO上は必須ではないと考えています。 オウチーノの事例ではSSRを導入してもインデックス状況は改善せず、パフォーマンス改善によってインデックス状況が改善しました。 またSSRしないと駄目だというならそもそも全くインデックスされないはずです。 現状もパフォーマンス改善のためにSSRを活用していますが、これはSSR導入済みだったためそれを活用しただけで、必須だったわけではありません。

SSRにすることでのメリットもありますが、メンテナンスコストは高めです。 一番のコストはSSRが壊れないようにすることです。Universal JSでない実装をしてしまって壊すことは日常的にありえますが、 SSRに失敗していてもCSRになるだけなので手元で開発していても気づかない場合があります。 現状はE2Eテストを使ってリリース前になるべく気づけるようにしてはいますが完璧というわけではありません。 またオウチーノの場合はhypernovaを利用しているため、hypernovaサーバーを別途運用する必要があります。 Docker化していてECSで運用しているためすごく手間というわけではありませんが・・・

RailsでSSRするのに疲弊しているように見えるけれど、Railsにのせる必要はあるの?

技術的な側面から言えば必要ではありません。エコシステムとの親和性を考えると、サーバー側はRailsではなくNode.jsがやりやすそうではあります。

一方でオウチーノは現在「全体をモノリスにする」という方針で進めています。 このあたりの背景については以前の記事をご参照ください。 https://developers.o-uccino.com/entry/2018/08/30/152838

今後チームが成熟するにつれてSPAは独立させることも検討しています。

sagaやReact-Headの部分は、あらかじめRails側でpropsを作って渡す形にすればシンプルになるのでは?

最初からSSRを想定した設計にするならそういったアプローチも可能だと思います。 SSRを想定しておらず、routingもすべてReact側にやらせている設計だったのと、SSRにすることで問題が解決するかどうかわからない状況下で 大きく設計を変更すべきでないと判断しこういった対応になりました。

これからそういった設計に変えることもできるのですが、どちらかというとSSRしなくて良い状態にできればベターと考えています。

E2Eテストはどうやってやっている?

変更がマージされた後まずRSpecやJestなどによる各種テストを行っています。 RSpecのfeature specとしてCapybara + Headless Chrome によるテストを行っています。 ただしこの段階ではhypernovaサーバーは使っておらずCSRによるテストです。

テストが通った後asset compileやdocker buildを経てstagingに自動デプロイされます。 stagingへのデプロイ後にE2Eテストをstagingに対して実行しています。 こちらはCapybara + PhantomJSを利用しています。

メジャーシナリオに対して期待した操作ができることに加え、SSRが実行された上でのhtmlが返却されていることや、意図通りのメタタグが出力されていることなどをテストしています。 このE2Eテストが通ったdocker imageに対してタグを付与しており、productionへはこのタグが付与されているものしかデプロイできないようになっています。

なおここでPhantomJSを利用しているのは苦肉の策です・・・ 以前Chromeでは問題ないのにSearch Console上ページがレンダリングされないというエラーが発生したことがあり、 PhantomJSだけがそのエラーを再現できたためこういった構成になっています。 どうやらGoogleのクローラーが利用しているChromeは最新というわけではないらしく、最新のHeadless Chromeを利用していると気づけないケースでした。 ただし現在PhantomJSの開発は停止しているため、Headless Chromeに戻すべきかどうかは検討中です。

このあたりのビルドパイプラインまわりはまた改めて記事にできればと思います。

最後に

いかがだったでしょうか。今後もオウチーノではユーザーにより使いやすいサービスを提供するために技術的なチャレンジを進めていきたいと考えています。 オウチーノでは一緒に働くメンバーを募集中です