はじめに
サーバー側レンダリング(SSR)は、サーバー上のクライアント側シングルページアプリケーション(SPA)をレンダリングし、完全にレンダリングされたページをクライアントに送信するための一般的な手法です。これにより、動的コンポーネントを静的HTMLマークアップとして提供できます。
このアプローチは、JavaScriptのインデックス作成が適切に行われない場合、検索エンジンの最適化(SEO)に役立ちます。また、大規模なJavaScriptバンドルのダウンロードが低速ネットワークによって損なわれている場合にも有効な場合があります。
このチュートリアルでは、Create React Appを使用しReactアプリを初期化した後、プロジェクトを変更してサーバー側レンダリングを有効にします。
このチュートリアルを終了すると、クライアント側 のReactアプリとサーバー側のExpressアプリを使用した作業プロジェクトが作成されます。
注意: 一方Next.jsは、Reactで構築された静的なサーバーレンダリングアプリケーションを作成するための最新のアプローチを提供します。
前提条件
このチュートリアルを実行するには、次のものが必要です。
- ローカルにインストールしたNode.js。Node.jsをインストールしてローカル開発環境を構築する方法を参照してください。
このチュートリアルは、Node v14.4.0、npm
v6.14.5で検証済です。
ステップ 1 — Reactアプリの作成とアプリコンポーネントの変更
まず、npxを用いて、最新バージョンのCreate React Appを使用して新しいReactアプリを起動します。
my-ssr-appアプリを呼び出しましょう。
- npx [email protected] my-ssr-app
次に、cd
で新しいディレクトリに移動します。
cd my-ssr-app
最後に、インストールを確認するために、新しいクライアント側アプリを起動します。
- npm start
ブラウザウィンドウに、サンプルReactアプリが表示されます。
それでは、<Home>
コンポーネントを作成しましょう。
- nano src/Home.js
次に、Home.js
ファイルに次のコードを追加します。
src/Home.js
import React from 'react'; export default props => { return <h1>Hello {props.name}!</h1>; };
これにより、nameに対して「Hello」
メッセージが付いた<h1>
見出しが作成されます。
次に、<App>
コンポーネントで<Home>
をレンダリングしましょう。App.js
ファイルを開きます。
- nano src/App.js
次に、既存のコード行をこれらの新しいコード行に置き換えます。
src/App.js
import React from 'react'; import Home from './Home'; export default () => { return <Home name="Sammy" />; };
これにより、name
が<Home>
コンポーネントに渡されるため、メッセージは、「Hello Sammy!」
と表示されるはずです。
このアプリのindex.js
ファイルでは、サーバー側のレンダリング後にアプリを再ハイドレーションすることをDOMレンダラーに示すために、render
の代わりに、ReactDOMのhydrate
メソッドを使用します。
index.js
ファイルを開きましょう。
- nano index.js
次に、index.js
ファイルの内容を次のコードに置き換えます。
index.js
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; ReactDOM.hydrate(<App />, document.getElementById('root'));
これで、クライアント側のセットアップは終了です。次にサーバー側のセットアップに進みます。
ステップ 2 — Expressサーバーの作成とアプリコンポーネントのレンダリング
アプリの準備が整ったので、レンダリングされたバージョンを送信するサーバーをセットアップしましょう。サーバーにはExpressを使用します。端末ウィンドウに次のコマンドを入力して、プロジェクトに追加しましょう。
- npm install [email protected]
または、yarnを使用して次のように行います。
- yarn add [email protected]
次に、アプリのsrc
ディレクトリの横にserver
ディレクトリを作成します。
- mkdir server
次に、Expressサーバーコードを含む新しいindex.js
ファイルを作成します。
- nano server/index.js
いくつかの定数を必要とし、定義するインポートを追加します。
server/index.js
import path from 'path'; import fs from 'fs'; import React from 'react'; import express from 'express'; import ReactDOMServer from 'react-dom/server'; import App from '../src/App'; const PORT = process.env.PORT || 3006; const app = express();
次に、エラー処理を含むサーバーコードを追加します。
server/index.js
// ... 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>
コンポーネントをサーバーのクライアントアプリから直接インポートすることができます。
ここでは3つの重要なことが起こっています。
build
ディレクトリのコンテンツを静的ファイルとして提供するようにExpressに指示します。ReactDOMServer
のrenderToString
メソッドを使用して、アプリを静的なHTML文字列にレンダリングします。- 次に、構築されたクライアントアプリから静的
index.html
ファイルを読み取り、id
が「root」
の<div>
にアプリの静的コンテンツを挿入し、リクエストへの応答として送信します。
ステップ 3 — webpack、Babel、およびnpm
スクリプトの設定
サーバーコードを機能させるには、webpackとBabelを使用して、サーバーコードをバンドルしてトランスパイルする必要があります。これを実行するには、端末ウィンドウに次のコマンド を入力して、プロジェクトに開発の依存関係を追加しましょう。
- npm install [email protected] [email protected] [email protected] @babel/[email protected] [email protected] @babel/[email protected] @babel/[email protected] --save-dev
または、yarnを使用して次のように行います。
- yarn add [email protected] [email protected] [email protected] @babel/[email protected] [email protected] @babel/[email protected] @babel/[email protected] --dev
注:このチュートリアルの以前のバージョンは、babel-core
、babel-preset-env
、およびbabel-preset-react-app
をインストールしました。これらのパッケージはその後アーカイブされ、代わりにモノリポジトリのバージョンが使用されます。
次に、Babelの設定ファイルを作成します。
- nano .babelrc.json
次に、env
とreact-app
プリセットを追加します。
.babelrc.json
{ "presets": [ "@babel/preset-env", "@babel/preset-react" ] }
注意:このチュートリアルの以前のバージョンでは、.babelrc
ファイル(.json
ファイル拡張子なし)を使用していました。これはBabel 6の設定ファイルでしたが、Babel 7では当てはまりません。
次に、Babel Loaderを使用してコードをトランスパイルするサーバーのwebpack設定を作成します。ファイルの作成から始めます。
- nano webpack.server.js
その後、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' } ] } };
この設定により、トランスパイルされたサーバーバンドルは、index.js
というファイルのserver-build
フォルダに出力されます。
webpack-node-externals
以降のtarget: 'node'
とexternals: [nodeExternals()]
の使用に注意してください。これは、バンドルのnode_modules
からファイルを除外します。サーバーはこれらのファイルに直接アクセスできます。
これにより、依存関係のインストールとwebpackおよびBabelの設定は完了です。
次に、package.json
に再度アクセスして、ヘルパーnpm
スクリプトを追加します。
- nano package.json
SSRアプリケーションを簡単に構築して提供するために、dev:build-server
、dev:start
、dev
scriptsをpackage.json
ファイルに追加します。
package.json
"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:*", ... },
サーバーに変更を加えた場合は、nodemon
を使用してサーバーを再起動します。そして、npm-run-all
を使用して複数のコマンドを並行して実行します。
端末ウィンドウで次のコマンドを入力して、これらのパッケージを今すぐインストールしましょう。
- npm install [email protected] [email protected] --save-dev
または、yarnを使用して次のように行います。
- yarn add [email protected] [email protected] --dev
このようにして、次のコマンドを実行して、クライアント側のアプリを構築し、サーバーコードをバンドルしてトランスパイルし、:3006
でサーバーを起動できます。
- npm run dev
または、yarnを使用して次のように行います。
- yarn run dev
サーバーのwebpack設定により変更を監視し、サーバーは変更時に再起動します。ただし、クライアントアプリの場合は、現在のところ、変更を加えるたびに構築する必要があります。ここに、未解決の課題があります。
ここで、Webブラウザでhttp://localhost:3006/
を開くと、サーバー側のレンダリングアプリが表示されます。
前回、ソースコードは次のように表示しました。
Output<div id="root"></div>
しかし今回は、変更を加えたことで、ソースコードは次のように表示します。
Output<div id="root"><h1 data-reactroot="">Hello <!-- -->Sammy<!-- -->!</h1></div>
サーバー側のレンダリングにより、<App>
コンポーネントがHTMLに正常に変換されました。
まとめ
このチュートリアルでは、Reactアプリを初期化し、サーバー側のレンダリングを有効にしました。
この投稿では、実行できることの内容に軽く触れただけです。ルーティング、データフェッチ、またはReduxもサーバーサイドのレンダリングアプリの一部になる必要があると、作業は少し複雑になりがちです。
SSRの使用の主な利点の1つは、JavaScriptコードを実行しないクローラーでも、コンテンツをクロールできるアプリケーションがあることです。これは、検索エンジン最適化(SEO)と、ソーシャルメディアチャネルへのメタデータの提供に役立ちます。
最初のリクエストで、サーバーから完全にロードされたアプリケーションが送信されるため、SSRはパフォーマンスの向上にもとても役立ちます。重要なアプリケーションの場合、SSRには少し複雑になる可能性のあるセットアップが必要であり、サーバーに大きな負荷がかかるため、有用性が異なる場合があります。Reactアプリにサーバー側のレンダリングを使用するかどうかは、特定のニーズと、どのトレイドオフがユースケースにとって最適であるかに依存します。
React について詳しく知りたい場合は、How To Code in React.js(React.js のコーディング方法) シリーズを参照するか、演習とプログラミングプロジェクトの React トピックページをご覧ください。