Reactアプリのサーバーサイドレンダリングを有効にする方法
警告:このチュートリアルは、ReactDOM.hydrate()とReactDOMServer.rendertoString()の簡単な紹介を目的としています。商用利用は意図していません。
一方、Next.jsはReactで構築された静的およびサーバーでレンダリングされたアプリケーションを作成するための現代的なアプローチを提供します。
紹介
サーバーサイドレンダリング (SSR) は、クライアントサイドの シングルページアプリケーション (SPA) をサーバー上でレンダリングし、完全にレンダリングされたページをクライアントに送信するための一般的な手法です。これにより、動的コンポーネントを静的なHTMLマークアップとして提供することができます。
このアプローチは、インデックス作成がJavaScriptを適切に処理しない場合に、検索エンジン最適化(SEO)に役立つことがあります。また、大きなJavaScriptバンドルのダウンロードが遅いネットワークによって妨げられる状況でも有益です。
このチュートリアルでは、Create React Appを使用してReactアプリを初期化し、その後、プロジェクトを変更してサーバーサイドレンダリングを有効にします。
このチュートリアルの最後には、クライアント側のReactアプリケーションとサーバー側のExpressアプリケーションを持つ作業プロジェクトが完成します。
前提条件
このチュートリアルを完了するためには、次のものが必要です:
- Node.jsがローカルにインストールされている必要があります。これは「Node.jsのインストール方法とローカル開発環境の作成方法」に従って行うことができます。
このチュートリアルは、Node v16.13.1、npm v8.1.2、react v17.0.2、@babel/core v7.16.0、webpack v4.44.2、express v4.17.1、nodemon v2.0.15、及びnpm-run-all v4.1.5で検証されました。
ステップ1 — Reactアプリの作成とアプリコンポーネントの修正
まず、最新バージョンのCreate React Appを使用して新しいReactアプリを立ち上げるためにnpxを使用します。
アプリを react-ssr-example と呼びましょう。
- npx create-react-app react-ssr-example
次に、新しいディレクトリにcdします:
cd react-ssr-example
最後に、インストールを確認するために新しいクライアントサイドアプリを起動します:
- npm start
ブラウザウィンドウに表示された例のReactアプリを見ることができます。
では、srcディレクトリ内に新しい<Home>コンポーネントを作成しましょう:
- nano src/Home.js
次に、Home.jsファイルに次のコードを追加してください:
function Home(props) {
return <h1>Hello {props.name}!</h1>;
};
export default Home;
これは、名前に向けて「Hello」と書かれた
見出しを作成します。
次に、<App>コンポーネントに<Home>をレンダリングしましょう。srcディレクトリ内のApp.jsファイルを開きます:
- nano src/App.js
次に、既存のコード行をこれらの新しいコード行に置き換えます。
import Home from './Home';
function App() {
return <Home name="Sammy"/>;
}
export default App;
これは name を <Home> コンポーネントに渡すので、表示されることを期待するメッセージは次の通りです:
"Hello Sammy!"
アプリの index.js ファイルでは、ReactDOM の hydrate メソッドを render の代わりに使用して、サーバーサイドレンダリングの後にアプリを再水和するつもりであることを DOM レンダラーに示します。
「src」ディレクトリにあるindex.jsファイルを開きましょう:
- nano src/index.js
次に、index.jsファイルの内容を以下のコードで置き換えてください:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.hydrate(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
これでクライアントサイドの設定は終了です。サーバーサイドの設定に移ることができます。
ステップ2 — Expressサーバーの作成とアプリコンポーネントのレンダリング
アプリが用意できたので、レンダリングされたバージョンを送信するサーバーを設定しましょう。サーバーにはExpressを使用します。
注: Create React App の react-scripts は、webpack-dev-server の要件として express のバージョンをインストールします。依存関係のツリーの競合を避けるため、このチュートリアルではもはやこのインストール手順を含めていません。
次に、プロジェクトのルートディレクトリに新しいserverディレクトリを作成します。
- mkdir server
それから、server ディレクトリの中に、新しい index.js ファイルを作成します。このファイルには Express サーバーのコードが含まれます:
- nano server/index.js
必要なインポートを追加し、いくつかの定数を定義してください:
import path from 'path';
import fs from 'fs';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import express from 'express';
import App from '../src/App';
const PORT = process.env.PORT || 3006;
const app = express();
次に、いくつかのエラーハンドリングを行ったサーバーコードを追加します:
// ...
app.get('/', (req, res) => {
const app = ReactDOMServer.renderToString(<App />);
const indexFile = path.resolve('./build/index.html');
fs.readFile(indexFile, 'utf8', (err, data) => {
if (err) {
console.error('Something went wrong:', err);
return res.status(500).send('Oops, better luck next time!');
}
return res.send(
data.replace('<div id="root"></div>', `<div id="root">${app}</div>`)
);
});
});
app.use(express.static('./build'));
app.listen(PORT, () => {
console.log(`Server is listening on port ${PORT}`);
});
<App>コンポーネントをサーバーから直接クライアントアプリにインポートすることが可能です。
ここで三つの重要なことが起こっています:
- Expressは、
buildディレクトリからコンテンツを静的ファイルとして提供するために使用されます。 ReactDOMServerのrenderToStringは、アプリを静的なHTML文字列にレンダリングするために使用されます。- ビルドされたクライアントアプリからの静的な
index.htmlファイルが読み込まれます。アプリの静的なコンテンツは<div>のidが"root"のところに挿入されます。これがリクエストへの応答として送信されます。
ステップ3 — webpack、Babel、およびnpmスクリプトの設定
サーバーコードを機能させるためには、webpackとBabelを使用して、それをバンドルし、トランスパイルする必要があります。これを達成するために。
注意:このチュートリアルの以前のバージョンでは、babel-core、babel-preset-env、およびbabel-preset-react-appがインストールされていました。これらのパッケージはすでにアーカイブされ、代わりにモノレポバージョンが使用されました。
Create React Appのreact-scriptsは、以下のパッケージのインストールを管理します:webpack、webpack-cli、webpack-node-externals、@babel/core、babel-loader、@babel/preset-env、@babel/preset-react。依存関係の木の競合を避けるために、このチュートリアルではこのインストール手順は含まれていません。
次に、プロジェクトのルートディレクトリに新しいBabel構成ファイルを作成します:
- nano .babelrc.json
次に、envとreact-appのプリセットを追加します:
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
]
}
注意: このチュートリアルの以前のバージョンでは、.babelrc ファイル (拡張子が .json ではない) を使用していました。これは Babel 6 のための設定ファイルでしたが、Babel 7 ではもはやそのようではありません。
さて、コードをトランスパイルするためにBabel Loaderを使用するサーバー用のwebpack設定を作成します。まず、プロジェクトのルートディレクトリにwebpack.server.jsファイルを作成します:
- nano webpack.server.js
次に、webpack.server.jsファイルに以下の設定を追加します:
const path = require('path');
const nodeExternals = require('webpack-node-externals');
module.exports = {
entry: './server/index.js',
target: 'node',
externals: [nodeExternals()],
output: {
path: path.resolve('server-build'),
filename: 'index.js'
},
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader'
}
]
}
};
この設定では、トランスパイルされたサーバーバンドルが server-build フォルダーに index.js という名前のファイルとして出力されます。
webpack-node-externalsからのtarget: 'node'とexternals: [nodeExternals()]の使用に注意してください。これにより、バンドル内のnode_modulesのファイルが省略され、サーバーはこれらのファイルに直接アクセスできます。
これで依存関係のインストールとwebpackおよびBabelの設定が完了しました。
では、package.jsonを再訪し、ヘルパーのnpmスクリプトを追加しましょう:
- nano package.json
dev:build-server、dev:start、およびdevスクリプトをpackage.jsonファイルに追加して、SSRアプリケーションを構築および提供します:
"scripts": {
"dev:build-server": "NODE_ENV=development webpack --config webpack.server.js --mode=development -w",
"dev:start": "nodemon ./server-build/index.js",
"dev": "npm-run-all --parallel build dev:*",
// ...
},
dev:build-server スクリプトは環境を "development" に設定し、以前に作成した設定ファイルを使用して webpack を呼び出します。 dev:start スクリプトは、ビルドされた出力を提供するために nodemon を呼び出します。
dev スクリプトは npm-run-all を呼び出して build スクリプトと dev:* で始まるすべてのスクリプト—dev:build-server や dev:start を含む—を parallel で実行します。
注: package.json ファイル内の既存の "start", "build", "test", および "eject" スクリプトを修正する必要はありません。
nodemon は、変更があったときにサーバーを再起動するために使用されます。 npm-run-all は、複数のコマンドを並行して実行するために使用されます。
それでは、ターミナルウィンドウに次のコマンドを入力して、これらのパッケージを今すぐインストールしましょう:
- npm install nodemon@2.0.15 --save-dev
- npm install npm-run-all@4.1.5 --save-dev
これを設定すると、次のコマンドを実行してクライアントサイドアプリをビルドし、サーバーコードをバンドルおよびトランスパイルし、:3006でサーバーを起動できます:
- npm run dev
サーバーのwebpack設定は、変更を監視し、変更時にサーバーを再起動します。ただし、クライアントアプリは、変更が加えられるたびに手動で再ビルドする必要があります。
注: その件についてはここにオープンな問題があります。
次に、ウェブブラウザでhttp://localhost:3006/を開き、サーバーサイドでレンダリングされたアプリを観察してください。
以前は、ソースコードを見ると次のことが明らかになりました:
<div id="root"></div>
しかし今、あなたが行った変更により、ソースコードは次のことを明らかにしています:
<div id="root"><h1 data-reactroot="">Hello <!-- -->Sammy<!-- -->!</h1></div>
サーバーサイドレンダリングは、<App> コンポーネントをHTMLに正常に変換しました。
結論
このチュートリアルでは、Reactアプリケーションを初期化し、サーバーサイドレンダリングを有効にしました。
この投稿では、可能性の表面をかすったにすぎません。ルーティング、データ取得、またはReduxがサーバーサイドレンダリングアプリの一部にならなければならないとき、事情は少し複雑になります。
SSRを使用する主な利点の一つは、JavaScriptコードを実行しないクローラーでも、コンテンツをクロールできるアプリを持つことです。これは、検索エンジン最適化(SEO)やソーシャルメディアチャンネルへのメタデータの提供に役立ちます。
SSRは、最初のリクエスト時に完全に読み込まれたアプリがサーバーから送信されるため、パフォーマンスの向上にも役立ちます。非自明なアプリの場合、SSRは設定が少し複雑になる可能性があり、サーバーにより大きな負荷をかけるため、結果は異なることがあります。Reactアプリにサーバーサイドレンダリングを使用するかどうかは、特定のニーズや、どのトレードオフがユースケースに最も適しているかによります。
Reactについてもっと学びたい場合は、私たちの「React.jsでのコーディング方法」シリーズを見てみるか、演習やプログラミングプロジェクトのためのReactトピックページをチェックしてください。