2016年9月のアメブロのシステムリニューアルでは、以下のような技術を使ってIsomorphic Web Applicationを実現しました。
実際にHTMLを生成する部分には、Reactを採用しました。Reactでは、サーバサイド、クライアントサイド両方のレンダリングに対応している他、コンポーネント化を強制するため、各ビューが独立し、管理がしやすくなります。
また、ライフサイクルが定義されていて、サーバサイドとクライアントサイドで共通の処理、クライアントサイドだけで実行したい処理といった記述を分けられるのもIsomorphic Web Applicationを作る上で役立ちます。なお、Reactの実装については連載第2回、Reactのサーバサイドレンダリングで苦労した話は連載第4回の連載で触れる予定です。
Reactのライフサイクルは、表示のタイミングに合わせて処理を分けられるので、Isomorphic Web Applicationと相性が良いです。
import React from 'react'; export class SampleComponent extends React.Component { static propTypes = { message: React.PropTypes.string.isRequired, }; constructor(props) { super(props); } componentWillMount() { // サーバサイド用。render前に実行される。constructorを使うのが推奨されている } componentDidMount() { // クライアントサイド用。初回の表示時 } shouldComponentUpdate(nextProps, nextState) { return this.props.message !== nextProps.message; } componentDidUpdate() { // クライアントサイド用。shouldComponentUpdateがtrueのときのみなので、SPAのページ遷移時に使われることが多い } componentWillUnmount() { // クライアントサイド用。イベントリスナやタイマー処理を消すなどする } render() { //サーバサイド、クライアントサイド両方 return ( <p>{this.props.message}</p> ); } }
アプリケーションの状態管理には、Reduxを利用しました。ReduxはFluxに影響を受けた“状態”管理のためのフレームワークです。アプリケーションで利用する“状態”と“処理”を分けることができます。サーバサイドでは“状態”を持たず、最初の“状態”を作り、HTMLに挿入するのみで、その“状態”はSPA時のクライアントサイドで変更されます。
Reduxによる実装については連載第2回で紹介する予定です。
//Node.js function renderPage(html, initialState) { return ` <!doctype html> <html> <body> <div id="app">${html}</div> <script> window.__INITIAL_STATE__ = ${JSON.stringify(initialState)} </script> <script src="/static/bundle.js"></script> </body> </html> ` }
サーバサイドで返却する際に初期“状態”(window.__INITIAL_STATE__)として渡されます。
※ Reduxのサンプルではシリアライズする際に、「JSON.stringify()」を利用していますが、JSONはJavaScriptの完全なサブセットではないため、エラーが起こる場合があります。また、「セキュリティの観点でも危険な文字列が入ってしまうこと」があります。その場合には「serialize-javascript」などを利用します。
データの取得においては、サーバサイドでは「node-fetch」を、クライアントサイドではWebpackによるビルドを行うときに「whatwg-fetch」を使って、「Fetch API」形式の記述にそろえています。
また、データ取得層で「Fetchr」を仲介して、サーバサイド、クライアントサイドともに、同様の記述で動作するようにしています。
このようにFetch APIとFetchrを使うことで、サーバサイドとクライアントサイドどちらも同じコード・同じフローで動くIsomorphic Fetchingを実現しています。
Webページのパスごとの分岐には「React Router」を利用しています。各パスに対応するReactコンポーネントが呼び出され、データ取得処理、レンダリングが行われます。
パスの設定自体は、サーバサイド、クライアントサイドともに共通ですが、サーバサイドでは、「エラーやリダイレクト時のレスポンス処理」、クライアントサイドでは、「ブラウザヒストリーの管理」「スクロール位置の管理」が必要です。
サーバサイドの実装は、Node.jsを使います。Node.jsは、アップデートは多いものの、2017年1月の原稿執筆時点では安定したプラットフォームです。「Isomorphic Web Applicationが実現できるのも、ここ数年におけるNode.jsの実績の成果である」ともいえます。
サーバサイド、クライアントサイドどちらでも同一のコードで動くようにするために、「Webpack」「Babel」を使ってコンパイルします。サーバサイド、クライアントサイドどちらでも同様に「require」できるように、「webpack-isomorphic-tools」を使っています。
Babelのプリセットはできるだけ最新バージョンのJavaScriptの記述を使えるようにしています。Node.jsの最新バージョンではほとんど対応されていますし、「確実に来る未来は早めに試そう」というポリシーを持って開発しています。
本連載のサンプルコードでは、ES2015とそれ以降の記述方法が、たびたび登場します。
// Template Strings const url = `https://api/${bloggerId}`; // Object matching shorthand const { message, image } = this.props; // Object Literal Property Value Shorthand const offset = 0; const limit = 10; const opts = { offset, limit, }; // Promise, Arrow function fetch(url) .then((res) => res.json()) .then((json) => console.log('Succeeded', json)) .catch((error) => console.error('Feiled', error)); // Default parameters function fetch(id, limit = 5) {} // Modules import { get } from 'lodash/get'; import * as bloggerAction from '../actions/bloggerAction';
最後に、Isomorphic Web Applicationを取り入れたことによるアメブロでの成果を挙げておきます。
Isomorphic Web Applicationでの初回表示、ページ遷移両方の表示速度向上により、メディア指標も上昇しました。Google Analyticsによる計測では、前月比(2016年8月と9月を比較)で「PV(Pageviews)が57.15%向上」「Pages / Session(1セッション当たりの閲覧ページ数)が35.54%向上」「Bouce Rate(直帰率)が44.44%削減」という結果になりました。
システムリニューアル以前の仕組みでは、リリースの際に、API、Webアプリケーション(Java)、クライアントサイド(JavaScript)全ての連携が必要でした。
システムリニューアル後には、データにまつわるアプリケーション(API)、Webサイトの表示に関わるアプリケーション(Isomorphic Web Application)の役割が分けられ、それぞれの単位でリリースが可能になり、自由度が増しました。
なお、DevOps関連については、連載第3回で触れる予定です。
ご覧のように、アメブロではIsomorphic Web Applicationを取り入れたことにより大きな成果を生み出すことができました。しかしながら、Isomorphic Web Applicationは簡単に作れるものでもなく、Webpackのビルドに頼る部分も多く過渡的な仕組みともいえます。
Isomorphic Web Applicationは魅力的なアーキテクチャの1つであることには違いありませんが、全てのサービスに適しているとはいえません。本稿をご自身のプロダクトに合っているのかどうかを判断する1つとして役立てていただけるとうれしいです。
次回は、ReactやReduxを使ったビュー部分の作り方についてお伝えする予定です。
2008年にサイバーエージェントに入社し、アメーバブログ、アメーバピグ、アメーバスマホなどのフロントエンドを担当。近年はサーバサイド・クライアントサイド両方の実装をしている。
共著書に『フロントエンドエンジニア育成読本』(技術評論社刊)、『Web制作者のためのGitHubの教科書』(インプレス刊)など。
Copyright © ITmedia, Inc. All Rights Reserved.