Microを使ってReactのSSRをする

先日、『[Node.js でちょっとしたサーバーサイドやるなら、 Micro が良いかも](https://qiita.com/acro5piano/items/d421e2d41ee15e20e1de)』を読みReactのSSRも簡単にできそうだなと感じたので試してみました。
以下の記事の完成したコードは[shisama/micro-react-ssr-sample](https://github.com/shisama/micro-react-ssr-sample)に置いています。

# Agenda
- [Micro](#Micro)
- [Setup](#Setup)
- [Hello, World!](#Hello-World!)
- [実践編](#実践編)


# Micro
[zeit/micro](https://github.com/zeit/micro)
[Next.js](https://nextjs.org/)を作っている[zeit](https://zeit.co/)が作ったNode.jsのフレームワークです。
『[Node.js でちょっとしたサーバーサイドやるなら、 Micro が良いかも](https://qiita.com/acro5piano/items/d421e2d41ee15e20e1de)』やzeitのブログ記事『[Micro 8: Better for Production, Easier for Development](https://zeit.co/blog/micro-8)』に詳しく載っています。

# Setup
microとReactをインストールします。
[micro](https://github.com/zeit/micro)はproduction用、[micro-dev](https://github.com/zeit/micro-dev)はdevelopment用です。

```
npm install --save micro
npm install --save-dev micro-dev
```

microとmicro-devを起動できるようにpackage.jsonに記載します。

```json: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のみでやってみます。

```js:index.js
module.exports = async (req, res) => {
    return 'Hello, World!' 
}
```

`npm start`を実行し、 http://localhost:3000 にアクセスすると`Hello, World!`が画面に表示されます。
また、`npm run dev`でmicro-devを実行するとHot Reloadingしてくれます。

次にReactを使って表示してみます。

```js
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`を作成します。
各々の設定の内容の意味は[こちら]()をご参考にしてください。

```json:.babelrc
{
  "presets": [
    [
      "env",
      {
        "targets": {
          "node": "current"
        }
      }
    ],
    "react"
  ]
}
```

## 非同期で取得したデータを表示する
次に非同期で取得したデータを表示させてみます。
今回はQiitaのAPIを使用します。
[Qiita API v2 documentation - Qiita:Developer](https://qiita.com/api/v2/docs)

データをリスト表示するだけのコンポーネントを作成します。

```jsx:List.jsx
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>
    );
};
```

```jsx:Item.jsx
import React from 'react';

export default (props) => {
    return ( 
        <li key={props.index}>
            {props.user.id}: <a href={props.url}>{props.title}</a>
        </li>
    );
}
```

次にサーバサイドでレンダリングするHTMLコンポーネントを作成します。

```jsx:Template.jsx
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](https://github.com/axios/axios)を使ってデータを取得します。
取得したデータは先程のHTML用のコンポーネントにJSON文字列として渡しています。
HTML用のコンポーネントは受けとったJSON文字列を`#qiita-data`の`data-json`というカスタム属性に格納しています。

```jsx:server.js
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`に格納しておいたデータを取得して表示するようにしています。

```jsx:client.js
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`にいかを記述し、実行します。

```json:package.json
"scripts": {
  "build:server": "babel src -d dist"
}
```

```
npm run build:server
```


## webpackでクライアント用のコードをバンドル
ブラウザで実行するためのクライアント用のコードをwebpackでbundleします

```js:webpack.config.js
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']
    }
}
```


```json:package.json
"scripts": {
  "build:client": "webpack"
}
```

```
npm run build:client
```


# Microでサーバ起動

最後にMicroでサーバを起動します。
本番実行時はmicroで実行

```
micro ${filepath}
```

開発時はmicro-devを実行するようにすると良いでしょう。

```
micro-dev ${filepath}
```


```json:package.json

"scripts": {
  "start": "micro dist/server.js",
  "dev": "micro-dev dist/server.js",
  "build:server": "babel src -d dist",
  "build:client": "webpack"
}
```

```bash:production
npm start
```

```bash:develop
npm run dev
```

ここまできたら http://localhost:3000 を開くとQiita APIで取得したデータが一覧で表示されると思います。

# Expressと比較
Expressで書いたサーバサイドのコードと比較してみます。
以下はExpressで実行するコードですが、Microの方がわずかにコード量は少なくシンプルではないでしょうか。

```jsx
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を触ってみて得た知見をアウトプットしていきましょう。

# 参考
- [Node.js でちょっとしたサーバーサイドやるなら、 Micro が良いかも](https://qiita.com/acro5piano/items/d421e2d41ee15e20e1de)
- [zeit/micro](https://github.com/zeit/micro)
- [React v16でのサーバーサイドレンダリング - blog.koba04.com](http://blog.koba04.com/post/2017/10/01/serverside-rendering-in-react-v16/)


最後までお読み頂きありがとうございました。
不備や質問があれば、コメント欄かTwitter([@shisama_](https://twitter.com/shisama_))までお願い致します。