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を書いた。
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に指定する。
if Rails.env.production?
Rails.configuration.react.server_renderer = ServerRenderer
else
Rails.configuration.react.server_renderer_options[:files] = ["components.js"]
end
これでHerokuのHobbyプランでも全く問題なくサーバーサイドレンダリングが動くようになった。