第2回 簡単なReact.jsの使い方 (8/8)

技術コラム

【第2回】簡単なReact.jsの使い方

OSS Radar Scope

8)サーバサイドレンダリング

インターネットに公開しているサイトではユーザビリティを考えてJavaScriptを多用しています。しかし、検索エンジンのクローラは一般的にはJavaScriptを解釈しません。そのため、ブラウザがあるURLにアクセスした場合にJavaScriptでレンダリングされるHTMLと、クローラが同じURLにアクセスした場合にサーバでレンダリングされるHTMLを同一にする必要があり、クライアントサイドとサーバサイドで同じロジックを別の言語で実装するなど涙ぐましい努力が行われてきました。

React.jsではブラウザのDOM を使わずに軽量なDOM を使用してるため、レンダリングにブラウザは不要です。JavaScriptエンジンを使用すれば、クライアントサイドのコードをほぼそのまま使用できるため、同一のロジックを異なる言語で実装する必要はありません。

今回実装したOSS Radar Scope®ではSEO のことも考えてサーバサイドレンダリングも行っています。サーバサイドはJavaで動いているので、Java8で利用出来るJavaScriptエンジンのNashornを利用しています。

しかし、React.jsのサーバサイドレンダリングはNode.js を想定しているようで、Nashornを単純に使用しても正しく動作しません。ここでは、NashornでReact.jsのサーバサイドレンダリングをする際にぶつかった壁について記載していきます。

requireが使用できない

まず一番最初の壁はrequireが使用できないことです。 Nashornはrequireが実装されていないため、最初にrequireが実行できるようにする必要があります。jvm-npmというライブラリがあり、この問題を解決できます。jvm-npmはloadで読み込ませます。

ScriptEngine engine = new ScriptEngineManager(null).getEngineByName("nashorn");
engine.eval("load('./dist/scripts/jvm-npm.js');");

React.jsが使用しているグローバル変数がNashornに存在しない

React.jsは内部で以下のグルーバル変数を利用しています。

  • console
  • console.warn
  • process
  • process.env
  • process.env.NODE_ENV

これらはNode.js にはあるようですが、Nashornには存在しません。したがってこれらをあらかじめJavaのNashorn内で定義しておく必要があります。

private void prepareForReactJs(ScriptEngine engine) throws ScriptException {
  engine.eval("var console = {warn: function() {}};");
  engine.eval("var process = {};");
  engine.eval("process.env = {};");
  engine.eval("process.env.NODE_ENV = 'production';");
}

Ajaxが実行できない

当初アクションの内部でAjax を実行していたのですが、Aciton内部にAjax があるとサーバサイドでレンダリングすることができません。Action内部でAjax を呼ばず、Router内でAjax を利用するように構造を変更しています。サーバサイドではあらかじめ取得した値を直接Router.run内で使用しています。

var ranking = Java.from(rankingList);
var categories = Java.from(categoryList);
var products = Java.from(productList);
var rankDates = Java.from(rankDateList).map(function(time) { return new Date(time); });
var props = {
  ranking: ranking,
  categories: categories,
  products: products,
  yearMonth: yearMonth,
  rankDates: rankDates,
  dotPosition: RadarStore.calcDotPosition(url, yearMonth, ranking, categories, isChildCategory)
};
var html;
Router.run(routes, url, function (Handlerargs) {
  html = React.renderToString(<Handler {...props}/>);
});
html;

作成したソースコード

これらの問題を解決し、無事にサーバサイドレンダリングができるようになりました。こちらで実装を見ることができますので、参考にしてください。