先日、『Node.js でちょっとしたサーバーサイドやるなら、 Micro が良いかも』を読みReactのSSRも簡単にできそうだなと感じたので試してみました。
以下の記事の完成したコードはshisama/micro-react-ssr-sampleに置いています。
Agenda
Micro
zeit/micro
Next.jsを作っているzeitが作ったNode.jsのフレームワークです。
『Node.js でちょっとしたサーバーサイドやるなら、 Micro が良いかも』やzeitのブログ記事『Micro 8: Better for Production, Easier for Development』に詳しく載っています。
Setup
microとReactをインストールします。
microはproduction用、micro-devはdevelopment用です。
npm install --save micro
npm install --save-dev micro-dev
microとmicro-devを起動できるようにpackage.jsonに記載します。
"scripts": {
"start": "micro",
"dev": "micro-dev"
}
reactとreact-domもインストールします。babelなどは不要です。
npm install --save react
npm install --save react-dom
Hello, World!
Hello, Worldを画面に出すまでやってみます。
まず、Reactを使わずmicroのみでやってみます。
module.exports = async (req, res) => {
return 'Hello, World!'
}
npm start
を実行し、 http://localhost:3000 にアクセスするとHello, World!
が画面に表示されます。
また、npm run dev
でmicro-devを実行するとHot Reloadingしてくれます。
次にReactを使って表示してみます。
const React = require('react')
const { renderToString } = require('react-dom/server')
module.exports = async (req, res) => {
return renderToString(React.createElement('div', null, 'Hello, World!'));
}
たったこれだけです。簡単ですね。
次はもう少し実践的なことをしてみましょう。
実践編
Babel Setup
ここからはJSXを使っていくためbabelを使います。
npm install --save-dev babel-cli babel-preset-env babel-preset-react
.babelrc
を作成します。
各々の設定の内容の意味はこちらをご参考にしてください。
{
"presets": [
[
"env",
{
"targets": {
"node": "current"
}
}
],
"react"
]
}
非同期で取得したデータを表示する
次に非同期で取得したデータを表示させてみます。
今回はQiitaのAPIを使用します。
Qiita API v2 documentation - Qiita:Developer
データをリスト表示するだけのコンポーネントを作成します。
import React from 'react';
import Item from './Item';
export default (props) => {
const list = props.items.map((item, index) => {
return <Item {...item} index={index} />
});
return (
<React.Fragment>
<ul>
{list}
</ul>
</React.Fragment>
);
};
import React from 'react';
export default (props) => {
return (
<li key={props.index}>
{props.user.id}: <a href={props.url}>{props.title}</a>
</li>
);
}
次にサーバサイドでレンダリングするHTMLコンポーネントを作成します。
import React from "React"
export default (props) => {
return (
<html>
<head>
<meta charset="UTF-8" />
<title>{props.title}</title>
</head>
<body>
<div id="app">{props.children}</div>
<script id="qiita-data" type="text/plain" data-json={props.data}></script>
<script src="./bundle.js" />
</body>
</html>
);
};
#qiita-data
の<script>
タグはあとから出てくる取得したデータを格納しておくための要素です。
次にサーバサイドのエントリポイントを用意します。
axiosを使ってデータを取得します。
取得したデータは先程のHTML用のコンポーネントにJSON文字列として渡しています。
HTML用のコンポーネントは受けとったJSON文字列を#qiita-data
のdata-json
というカスタム属性に格納しています。
import React from 'react';
import { renderToString } from 'react-dom/server';
import axios from 'axios';
import Template from './Template';
import List from './List';
export default async (req, res) => {
const apiRes = await axios.get('https://qiita.com/api/v2/items');
const data = apiRes.data;
return renderToString(
<Template title="Qiita API List" data={JSON.stringify(data)}>
<List items={data} />
</Template>
)
}
クライアントサイド用にコードを用意します。
サーバ側で取得して#qiita-data
に格納しておいたデータを取得して表示するようにしています。
import React from 'react';
import { hydrade } from 'react-dom';
import Template from './Template';
import List from './List';
const items = JSON.parse(document.querySelector('#app').getAttribute('data-json'));
hydrade(
<Template title="Qiita API List">
<List items={items} />
</Template>,
document.querySelector('#app')
);
Babelでサーバサイド用のコードをトランスパイル
これまでに作成したファイルをsrc
ディレクトリに格納します。
サーバサイド用にBabelでトランスパイルします。
package.json
にいかを記述し、実行します。
"scripts": {
"build:server": "babel src -d dist"
}
npm run build:server
webpackでクライアント用のコードをバンドル
ブラウザで実行するためのクライアント用のコードをwebpackでbundleします
const path = require('path');
module.exports = {
mode: 'production',
entry: {
bundle: path.resolve(__dirname, 'src', 'client.js')
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: "[name].js"
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel-loader'
}
]
},
resolve: {
extensions: ['.js', '.jsx']
}
}
"scripts": {
"build:client": "webpack"
}
npm run build:client
Microでサーバ起動
最後にMicroでサーバを起動します。
本番実行時はmicroで実行
micro ${filepath}
開発時はmicro-devを実行するようにすると良いでしょう。
micro-dev ${filepath}
"scripts": {
"start": "micro dist/server.js",
"dev": "micro-dev dist/server.js",
"build:server": "babel src -d dist",
"build:client": "webpack"
}
npm start
npm run dev
ここまできたら http://localhost:3000 を開くとQiita APIで取得したデータが一覧で表示されると思います。
Expressと比較
Expressで書いたサーバサイドのコードと比較してみます。
以下はExpressで実行するコードですが、Microの方がわずかにコード量は少なくシンプルではないでしょうか。
import React from 'react';
import { renderToString } from 'react-dom/server';
import axios from 'axios';
import Template from './Template';
import List from './List';
import express from 'express';
const app = express();
app.get('/', async(req, res) => {
const apiRes = await axios.get('https://qiita.com/api/v2/items');
const data = apiRes.data;
renderToString(
<Template title={title} data={JSON.stringify(data)}>
<List items={data} />
</Template>
).pipe(res);
});
app.listen(3000);
所感
Microはまだ出てきたばかりで知見がない分、現時点ではExpressを選ぶ方がいいかもしれません。
しかし、micro-devはHot Reloadingをしてくれるし、ログ表示もわかりやすいです。
Next.jsやNowを作ったZEIT製なので期待はできます。
知見がインターネットに転がり始めたら使ってもいいのではないでしょうか。
そのためにもMicroを触ってみて得た知見をアウトプットしていきましょう。
参考
最後までお読み頂きありがとうございました。
不備や質問があれば、コメント欄かTwitter(@shisama_)までお願い致します。