💨
React, Express でSSRを実現する
CSR(Client Side Rendering) と SSR (Server Side Rendering)
Create React App を使って作成されるアプリケーションは CSR(Client Side Rendering)で描画されます。CSR の場合、ブラウザは空の HTML をロードした後 JavaScript ファイルをロードしてコンポーネントを描画します。一方 SSR では、最初にサーバーサイド側で静的なページとして HTML を描画し、動的な JavaScript を後から注入します(hydration)。先に静的なページが表示されるため、初期描画の速度が上がります。
React における Server Side Rendering
ReactDOMServer.renderToString()
というメソッドを利用することで、React Component をサーバー上で HTML として扱うことができ、hydrateRoot
を利用することでその HTML に JavaScript をアタッチしてインタラクティブな動作を実現できます。
処理手順としは以下のようになります。
- サーバーサイドで構築された HTML を静的なページとしてブラウザに描画する。
- ブラウザは動的なページを構築するために必要な JavaScript をダウンロードする。
- コンポーネントの JavaScript がロードされた時点で、React は静的なページを動的なページに入れ替える。この時 1 で描画された DOM を再利用しつつ、イベントハンドラのみを設定する。
- イベントハンドラが設定され、最終的にユーザーはコンポーネントを操作できるようになる。
具体的な実装
具体的な実装を見ていきます。
リポジトリは以下にあります。
まずは最終的に描画したい簡単なコンポーネントを用意します。
src/App.jsx
import React, { useEffect, useState } from "react";
export const App = () => {
const [clientMessage, setClientMessage] = useState("");
const [count, setCount] = useState(0);
useEffect(() => {
setClientMessage("Hello World");
}, []);
return (
<>
<h1>{clientMessage}</h1>
<h2>{count}</h2>
<button onClick={() => setCount((prev) => prev + 1)}>+Click</button>
</>
);
};
export default App;
次に、hydrate を行います。
React18 以降ではhydrateRoot を使えば、hydrate を行うことができます。
これにより、サーバーで描画されたただの HTML がインタラクティブなものになります。
src/index.jsx
import React from "react";
import { hydrateRoot } from "react-dom/client";
import App from "./App";
const container = document.getElementById("root");
hydrateRoot(container, <App />);
最後に、サーバーの処理です。
サーバーでは、初期に描画させたいコンポーネントを HTML として用意し、レスポンスします。
server/index.js
import express from "express";
import React from "react";
import ReactDOMServer from "react-dom/server";
import App from "../src/App";
const PORT = process.env.PORT || 4000;
const app = express();
app.get("/", (req, res) => {
// AppコンポーネントをHTML文字列に変換
const app = ReactDOMServer.renderToString(<App />);
// HTMLに変換されたAppコンポーネントを埋め込んだHTMLを作成
const html = `
<html lang="en">
<head>
<script src="client.js" async defer></script>
</head>
<body>
<div id="root">${app}</div>
</body>
</html>
`;
// コンポーネントが埋め込まれたHTMLをレスポンス
res.send(html);
});
app.use(express.static("./build"));
app.listen(PORT, () => {
console.log(`Server is listening on port ${PORT}`);
});
動作確認
npm run start
を実行して、http://localhost:4000 にアクセスします。
Hello World
という文字列と count up できる数字が表示されていれば成功です。
Discussion