Rails
Heroku
React

Herokuでreact-railsのサーバーサイドレンダリングがTimeoutしてハマった話

More than 1 year has passed since last update.

react-railsでサーバーサイドレンダリングを有効にしてHerokuにデプロイしたらリクエストがタイムアウトでエラーになるようになった。Rails内でV8動かしてるからさすがにメモリ足りないのかなーと思って2X dynoにしてみたけどダメで、Performance-L dynoっていう14GB RAMの最強のやつ($500/month)にしたらギリギリ返ってくるようになった。

が、よくよく考えてみるとそんなにメモリ必要なわけないよなーと思って、コード読んだりしてみた結果、browserifyのビルドがリクエスト中に実行されるのが原因だった。

react-railsはサーバーサイドのRednererをconfig.react.server_rendererで入れ替えられるようになっていて、これのデフォルトがSprocketsRendererっていうやつで、こいつはリクエスト時にSprocketsのビルドを実行してサーバーサイドレンダリング用のコードを生成するってやつになっている。

で、今回browserify-railsっていうSprockets内でbrowserifyを実行してビルドするっていうgemを使っていたせいで、リクエスト内でbrowserifyのビルドが走ってTimeoutしていた。

これを解決するのに、こんな感じのRendererを書いた。

lib/autoload/server_renderer.rb
class ServerRenderer < React::ServerRendering::ExecJSRenderer
  def initialize(options = {})
    super(options.merge(code: js_code))
  end

  def render(component_name, props, prerender_options)
    if !props.is_a?(String)
      props = props.to_json
    end

    super(component_name, props, render_function: 'renderToString')
  end

  private

  def js_code
    manifest_dir = Rails.application.assets_manifest.dir
    filename = Rails.application.assets_manifest.assets["components.js"]
    filepath = File.join(manifest_dir, filename)
    File.read(filepath)
  end
end

これは何をしているかというと、サーバーサイドレンダリング用のJS(components.js)をassets:precompileでデプロイ時にビルドしておいて、そのコードをmanifestから探してきてセットしているだけ。

んでproductionではこいつをRendererに指定する。

config/initializers/react.rb
if Rails.env.production?
  Rails.configuration.react.server_renderer = ServerRenderer
else
  Rails.configuration.react.server_renderer_options[:files] = ["components.js"]
end

これでHerokuのHobbyプランでも全く問題なくサーバーサイドレンダリングが動くようになった。