サーバーサイドレンダリングができること
さて、シングルページアプリケーションの苦手なところを解決するために、いくつもの手法が編み出されました。そのうちの1つがサーバーサイドレンダリングと呼ばれる技術です(図3)。
サーバーサイドレンダリングにおいて、HTMLデータは静的ファイルを配信するものではなく、Node.jsサーバーによって動的に生成されるものです。コンテンツが埋め込まれたHTMLデータを生成することで、クローラーにもちゃんとコンテンツを見せることができます。
本来であれば、ブラウザ側で初期表示される際に生成されるはずだったDOMツリーを、サーバー側で動的に生成し、HTMLデータに埋め込んだ状態でサーバーから返却するのです。生成には、ReactDOMと同じパッケージに入っているReactDOMServerをNode.js上で使用します。リスト2のコードを実行することで、Virtual DOMから生成されたDOMツリーをHTML文字列として取得することができるので、これを返却するHTMLデータに埋め込みます。
import ReactDOMServer from 'react-dom/server'; import App from '../src/App.js'; // ブラウザ向けのコンポーネント // Reactにより生成されたHTML文字列を取得する const appString = ReactDOMServer.renderToString(<App />); // appStringをHTMLデータに埋め込んで、ブラウザに返却する
ReactDOMの仕組みを使用する性質上、これはNode.jsなどの、サーバーサイドJavaScriptの処理系でしか実行できません。
さて、これによって生成されたHTMLデータは、リスト3の形でブラウザに渡されます。
<!DOCTYPE html> <html lang="en"> <head> <!-- 略 --> <title>React App</title> </head> <body> <noscript> You need to enable JavaScript to run this app. </noscript> <div id="root"> <!-- <App />に相当する部分 --> <div> <!-- (1) ここから--> Hello, React! </div> <!-- (1) ここまで --> </div> <script type="text/javascript" src="//cz-cdn.shoeisha.jp/static/js/main.a285be49.js"> <!-- (2) --> </script> </body> </html>
(1)がサーバー側で埋め込まれた部分です。ブラウザが読み込むためのJavaScriptは(2)のように従来通り存在していますが、それを先にサーバー側のアプリケーションでも読み込んでおいて、(1)の部分を生成している形になります。
読み込み後の流れ
HTMLデータには初期表示を実現できるだけのコンテンツが埋め込まれているので、ひとまずJavaScriptのダウンロードを始める前に画面を表示できます。これで、サイズが大きいJavaScriptファイルは、画面が表示されてから後ろで読み込めば良いことになります。
JavaScriptのダウンロードが終わると、従来通りにReactDOMがVirtual DOMの構築を始めます。これが終わると、現状のDOMツリーとの差分を見て、違いがあった場合だけVirtual DOMの内容でDOMツリーを更新します。それ以降は、通常のシングルページアプリケーションと同様に、DOMツリーはVirtual DOMの管理下に置かれます。
この流れで初期化を行う場合は、ReactDOM.render()
ではなく、同じAPIを持ったReactDOM.hydrate()
を使うことが推奨されています。リスト4のように、render()
を使った場合と使い方は同じです。
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; ReactDOM.hydrate( <App />, document.getElementById('root') );
React側でもサーバーサイドレンダリングを意識した機能を作ってくれているのは、うれしいところですね。
技術的に難しいところ
さて、サーバーサイドレンダリングでシングルページアプリケーションの苦手な点をい解消できそうですが、いくらかの難点も抱えています。
ブラウザで動かす前提のコードをNode.js上で動かすのは簡単ではない、といった点が、その最たるものです。ブラウザでもNode.jsでも動くようにツールを作る、IsomorphicやUniversal JavaScriptと呼ばれる概念が浸透してきたおかげで、両者で動くライブラリは増えてきていますが、そうでないライブラリも多くあります ブラウザにあってNode.jsにない機能の筆頭として、DOM APIがあります。DOMを操作する入り口であるdocument
がないので、DOMを操作するタイプの、jQueryなどのライブラリが初期表示時に動く構成になっていると、サーバーサイドレンダリングは実現しづらくなります。
また、create-react-appで作られたプロジェクトのように、Babelによって言語仕様を調整されているJavaScriptコードを、Node.jsが直接読むことはできません。サーバー側のNode.jsがcreate-react-app相当の言語仕様を解釈できるように、適切にBabelなどでコードを変換する必要があります。