Edited at

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

More than 3 years have 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プランでも全く問題なくサーバーサイドレンダリングが動くようになった。