Create React App で SSR してみた。
どうも fujihara です。あけましておめでとうございます。今年もよろしくおねがいします。 本日は勉強がてら Create React App で初回に表示されるものを Server Side Rendering する方法をご紹介します。
はじめに
Create React Appでプロジェクト作ると、いい感じの開発環境できるんですが、 サーバサイドレンダリングはしてくれていないので、どうにかできないかと思って 初期段階のもので設定してみました。 webpack.config.js などの設定は要リファクタリングです。 また自動生成された src 配下は極力いじらないようにしてあります。
必要モジュールのインストール
Create React App した時点でかなりのモジュールがインストールされているので必要な分だけインストールします
$ yarn add webpack-cli express nodemon node-style-loader webpack-node-externals babel-loader@8.0.4
2019/01/17時点で babel-loaderは 8.0.4 じゃないとwebpack 実行時にエラーが出ます。エラーが出た場合は指定された
babel-loader をインストールし直してください。
ディレクトリ・ファイルの追加
dist クライアント用jsの出力先
server サーバー用jsの出力先
src/server.js サーバー用js
webpack.config.js webpack設定ファイル
追加ファイル
webpack.config.js クライアントとサーバ用のもの設定してあります。
const webpack = require('webpack')
const path = require('path')
const webpackNodeExternals = require('webpack-node-externals')
module.exports = [
{
output: {
path: path.resolve("dist"),
publicPath: "/",
filename: "build.js"
},
entry: "./src/index.js",
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ['@babel/preset-env', '@babel/preset-react']
}
}
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.svg$/,
//img タグで srcプロパティに直接 import し代入できる
use: ['@svgr/webpack', 'url-loader']
}
]
}
},
{
output: {
path: path.resolve("server"),
filename: "server.build.js"
},
entry: "./src/server.js",
//以下2行 node 用宣言
target: 'node',
externals: webpackNodeExternals(),
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ['@babel/preset-env', '@babel/preset-react']
}
}
},
{
test: /\.css$/,
use: [
// node用
'node-style-loader',
'css-loader'
]
},
{
test: /\.svg$/,
//img タグで srcプロパティに直接 import し代入できる
use: ['@svgr/webpack', 'url-loader']
}
]
}
}
]
server.js
import express from 'express'
import React from 'react'
import App from './App'
//読み込んだコンポーネントを出力用に変換
import { renderToString } from 'react-dom/server'
//スタイルを取得
import { collectInitial } from 'node-style-loader/collect'
import './index.css'
const app = express()
//静的ファイル
app.use(express.static('dist'))
app.get('*', (req, res) => {
const markup = renderToString(<App/>)
//style 取得 tagで取得できる
const initialStyleTag = collectInitial()
// html 出力 (markup , initialStyleTag, クライアント用js )
res.send(`<html>
<head>
<title>ssr</title>
${initialStyleTag}
</head>
<body><div id="root">${markup}</div><script src="/build.js" defer></script></body>
</html>
`)
})
app.listen(process.env.PORT || 3000)
src/index.jsの修正
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
// サーバ内で出力されたスタイルタグを消す
import serverStyleCleanup from 'node-style-loader/clientCleanup'
ReactDOM.hydrate(<App />, document.getElementById('root'));
//SSRされたstyleタグを削除
serverStyleCleanup()
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();
実行
package.jsonの scripts に "webpack -w --mode development & nodemon server/server.build.js " を登録してあげると楽です。 localhost:3000 で確認できます。以下出力された html ソースです。レンダリングされていますね。
まとめ
個人的には後からSSRに設定するよりは最初に設定してしまう方が好みです。 実際はここから色んなカスタマイズが待ってるんですが、参考になれば良いです。 まあ、 Next.js, React Starter Kit 使ってって話ですかね。