【React】viteでのSSR/CRSサンプル
更新日:2026/01/29
Reactはブラウザ上で動作するJavaScriptライブラリです。
ですが、Reactはサーバー上でも動作します。
ブラウザでのレンダリングはCSR(クライアントサイドレンダリング)と呼ばれています。
一方、サーバー上でのレンダリングはSSR(サーバーサイドレンダリング)と呼ばれています。
今回は、超高速フロントエンドビルドツールのViteで、Reactを使用したSSR/CSRのサンプルコードを作成しました。
- 1前提条件
- 2サーバー側の環境
- 1:Node.jsを実行可能
- 2:ポートの占有とサーバー上で常駐
- 3ReactのSSR側の役割
- 4SSR/CSRの二つのパターン
- パターン1:ハイドレーションなしSSR
- パターン2:ハイドレーションありSSR
- 5ハイドレーションなしSSRのコード
- 準備
- ファイル構成
- index.html
- server.js
- SSR側コード
- CSR側コード
- package.json
- vite.config.js
- テスト
- ビルド
- デプロイ(サーバーへのアップロード)
- 6ハイドレーションありSSRのコード
- 準備
- ファイル構成
- index.html
- entry-ssr.jsx
- entry-csr.jsx
- その他の情報
- 7コードのダウンロード/デモ
前提条件
SSR側でhtmlファイルをそのまま返したり、Reca以外のフレームワークでレンダリングすることも可能です。
同様にCSR側でReactを使用しない構成もできます。
どちらか片方でReactを使っていれば、ReactでのSSR/CRSと言っていいかもしれません。
ですが、今回はSSR/CSRともにReactのコンポーネントを使ってレンダリングするケースを紹介します。
サーバー側の環境
Reactをブラウザ上で動作させるときは、ビルド結果をそのまま使用するだけです。
そのため、レンタルサーバーでも比較的容易にReactを導入できます。
プロの技術者なら当たり前と感じるかもしれませんが、サーバーに必要な環境を挙げます。
1:Node.jsを実行可能
Reactをサーバー上で動作させるために、サーバーにNode.jsをインストールする必要があります。
そのため、Node.jsをインストールできないレンタルサーバーではSSRを実現できません。
2:ポートの占有とサーバー上で常駐
ブラウザからの要求を処理するために、ポートの占有が必要です。
WordPressホスティングサービスに特化したレンタルサーバーは、ポートを開放していないことが多いです。
また、作成したプログラムをサーバー上で常駐させる必要があります。
ポートが占有できても、長時間の常駐が許可されない可能性があります。
ReactのSSR側の役割
ReactのSSRとしての機能は、コンポーネントを処理して最終的にHTMLテキストを出力するだけです。
Reactはブラウザからの要求を処理する機能はありません。
そのため、Node.jsのhttpモジュールやExpress等のWebアプリケーションフレームワークを利用します。
SSR/CSRの二つのパターン
SSRには大きくわけて、ハイドレーションなしとハイドレーションありの2つの運用パターンがあります。
パターン1:ハイドレーションなしSSR
ハイドレーションなしSSRは、クライアント側でReactがレンダリングする範囲を、サーバー側で生成しないパターンです。
サーバー側は、クライアント側のReactがかかわらない範囲をレンダリングします。
場合によっては、ReactのコンテナをSSR側で生成することもあります。
サーバー側の処理を短くできますが、クライアント側は初期DOM生成後にReacがレンダリングを開始するためファーストビュー表示が遅くなる可能性があります。
またSEOを重視している場合は、サーバーで生成したHTMLの内容が薄くなるため不利になります。
利点は、SSRとCSRを完全に分離できるため、Hydration mismatch(SSR側とCSR側のレンダリング結果が不整合)を構造的に回避できる点です。
チームで開発しているなら、SSRとCSRの担当を明確にわけることができます。
ブラウザ側では、createRoot()でエントリポイントを指定します。
パターン2:ハイドレーションありSSR
ハイドレーションありSSRは、サーバー側でクライアント側のレンダリングを行うパターンです。
クライアント側は、サーバーから受け取ったHTML(実際には生成後のDOM)を引き継ぎます。
ハイドレーションなしSSRと比較するとサーバー側の負担が大きくなりますが、クライアント側で完全な(または近い)初期画面をユーザーに提示できます。
多くのケースで、SSR側とCSR側で同じコンポーネントを利用できます。
ただし、Hydration mismatchを引き起こす可能性があります。
改修によって Hydration mismatch が顕在化することもあるので、一度沼にはまると非常に苦労します。
ブラウザ側では、hydrateRoot()でエントリポイントを指定します。
ハイドレーションなしSSRのコード
最初にハイドレーションなしSSRのコードを紹介します。
https://note.affi-sapo-sv.com/demo/vite-react-non-hydrated-ssr/
■GitHub
https://github.com/kchan-p/vite-react-non-hydrated-ssr
準備
まずは開発環境を作成します。
npm create vite@latest vite-react-non-hydrated-ssr -- --template react-compiler
cd vite-react-non-hydrated-ssr
npm install express --save-dev
言語をJavaScriptで、さらにReactアプリケーションを自動的に最適化してくれるReact Compilerを使用します。
TypeScriptを利用したい場合、次のページを呼んでください。
今回はミドルウェアとしてexpressを利用します。
expressはビルド時にバンドルしないので、--save-devを指定してインストールします。
ファイル構成
最終的なファイル構成です。
vite-react-non-hydrated-ssr ┣━ dist ← ビルド結果 ┃ ┣━ csr ┃ ┃ ┣━ assets ┃ ┃ ┃ ┣━ index-BmbLjjn_.js ┃ ┃ ┃ ┗━ index-tn0RQdqM.css ┃ ┃ ┗━ index.html ┃ ┗━ ssr ┃ ┗━ entry-ssr.js ┣━ public ┣━ src ┃ ┣━ assets ┃ ┣━ csr ← CRS側コード ┃ ┃ ┣━ csr.css ┃ ┃ ┣━ csr.jsx ┃ ┃ ┗━ entry-csr.jsx ┃ ┗━ ssr ← SSR側コード ┃ ┣━ entry-ssr.jsx ┃ ┣━ rendertohtml.js ┃ ┗━ ssr.jsx ┣━ README.md ┣━ eslint.config.js ┣━ index.html ┣━ package-lock.json ┣━ package.json ┣━ server.js ← サーバー常駐コード ┗━ vite.config.js
index.html
index.htmlはクライアント側ビルド時の基点および、SSR側のテンプレートです。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>Vite React SSR - Non-hydrated SSR -</title>
</head>
<body>
<h1>Vite React SSR - Non-hydrated SSR -</h1>
<!--ssr-->
<div id="csr"></div>
<script type="module" src="/src/csr/entry-csr.jsx"></script>
</body>
</html>
index.htmlはserver.jsで読み込まれ、<!--ssr-->とSSRで生成されたHTML文字列が置換されます。
そして置換後の文字列をレスポンスとして返します。
<div id="csr"></div>は、CSR側のエントリポイントです。
server.js
server.jsは、サーバーで常駐してブラウザからの要求を処理します。
import express from "express";
import fs from "fs";
import path from "path";
import { pathToFileURL } from "url";
process.env.NODE_ENV ??= "production";
const isProd = process.env.NODE_ENV === "production";
const app = express();
const root = process.cwd();
const [template, render] = await (async () => {
if (isProd) {
/**
* 本番環境
*/
app.use(
"/assets",
express.static(path.join(root, "dist/csr/assets"))
);
const template = fs.readFileSync(
path.join(root, "dist/csr/index.html"),
"utf-8"
);
const importModule = await import(
pathToFileURL(path.join(root, "dist/ssr/entry-ssr.js")).href
);
return [template, importModule.default];
}
/**
* Vite開発サーバー
*/
const { createServer } = await import("vite");
const vite = await createServer({
server: { middlewareMode: true },
appType: "custom",
});
app.use(vite.middlewares);
const template = async (url) => {
const rawTemplate = fs.readFileSync("index.html", "utf-8");
return await vite.transformIndexHtml(url, rawTemplate);
};
const render = async () => {
const importModule = await vite.ssrLoadModule("/src/ssr/entry-ssr.jsx");
return await importModule.default();
};
return [template, render];
})();
app.use(async (req, res) => {
try {
const appHtml = await render();
const templateHtml = isProd ? template : await template(req.originalUrl);
const html = templateHtml.replace("<!--ssr-->", appHtml);
res.status(200).set({ "Content-Type": "text/html" }).end(html);
} catch (e) {
console.error(e);
res.status(500).end("Internal Server Error");
}
});
app.listen(3000, () => {
console.log(`Server running: http://localhost:3000`);
});
今回はexpressを利用しているので、大枠はexpressのメソッド呼び出しです。
こちらについては、他のネット記事を参照してください。
次のコードで、開発(development)と本番(production)の切り分けをしています。
process.env.NODE_ENV ??= "production";
const isProd = process.env.NODE_ENV === "production";
process.envは環境変数を参照できます。
最初の行は、環境変数NODE_ENV のデフォルト値をproduction(本番)にしています。
開発時は次のように環境変数NODE_ENVにdevelopmentをセットしてから、server.jsを起動します。
package.json
"scripts": {
"dev": "set NODE_ENV=development&& node server.js",
上記のコードを見るとわかりますが、server.jsはnode.jsが実行します。
そのためvite管理外となるので、開発時はviteの開発サーバーを起動させます。
viteの開発サーバーを起動することで、jsxファイルを動的にESMに変換してくれるのでビルド無しで実行できるようになります。
ブラウザから要求を受け取ったら、entry-ssr.jsxのレンダリング結果をindex.htmlの"<!--ssr-->"と置き換えて、ブラウザに返します。
本番時は、ファイルパス固定でindex.htmlとentry-ssr.jsを読み込みます。
ファイルパスにはdistフォルダが含まれるので、おや?と感じるかもしれませんが、server.jsはビルド対象ではないのでdistフォルダに出力されません。
本番サーバーには、server.jsとdistフォルダをそのままの構造でアップロードします。
SSR側コード
entry-ssr.jsxは、ReactコンポーネントをHTMLテキストに変換しています。
/src/ssr/entry-ssr.jsx
import { renderToHtml } from "./rendertohtml";
import Ssr from "./ssr";
function htmlEncode(str) {
return String(str)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
};
async function render() {
const html = await renderToHtml(<Ssr />);
return `${html}
<p>------------------------------------<p>
<p>SSR側で生成したHTML</p>
<code>
${ htmlEncode(html)}
</code>
<p>------------------------------------</p>`;
}
export default render
renderToHtml()は、ReactコンポーネントをHTMLテキストに変換する自作関数です。
実際の運用では、取得したHTMLテキストをそのまま返すのが正道なんですが、今回はレンダリング結果をブラウザ上で確認したかったので後から加工しています。
rendertohtml.jsは、renderToHtml()を定義しています。
/src/ssr/rendertohtml.js
import { renderToPipeableStream } from "react-dom/server";
function renderToHtml(element) {
return new Promise((resolve, reject) => {
let html = "";
const decoder = new TextDecoder();
const { pipe } = renderToPipeableStream(element, {
onAllReady() {
const writable = {
write(chunk) {
html += decoder.decode(chunk, { stream: true });
},
end() {
html += decoder.decode();
resolve(html);
},
};
pipe(writable);
},
onError(err) {
reject(err);
},
});
});
}
export {renderToHtml}
renderToPipeableStream()は、レンダリング結果をNode.jsのストリームに送り込むことを目的とした関数です。
この関数は非同期で動作しますが、Promiseで同期させています。
同期させることで、実質的にはrenderToString()と同等になっています。
そのためrenderToString()を使用する場面ですが、こちらの関数は将来的に非推奨になりそうなので、renderToPipeableStream()を使用しています。
最後に、ssr.jsxです。
コンポーネントを読み込んでいることを例示するだけなので、適当です。
/src/ssr/ssr.jsx
function Ssr() {
return (
<div id="header">
<h2>SSRが生成したヘッダー</h2>
</div>
);
}
export default Ssr
CSR側コード
entry-csr.jsxは、いつものエントリポイント指定です。
注意点はcreateRoot()を使用する点です。
/src/csr/entry-csr.jsx
import { createRoot } from 'react-dom/client'
import './csr.css'
import Csr from './csr.jsx'
const container = document.getElementById("csr");
if (container) {
createRoot(container).render(<Csr />);
}
createRoot()によりコンテナ内がクリアされ、コンポーネントがレンダリングされます。
csr.jsxはコンポーネントを読み込んでいることを例示するだけなので、適当です。
/src/csr/csr.jsx
import { useState } from "react";
function Csr() {
const [count, setCount] = useState(0);
return (
<>
<p>------------------------------------</p>
<p>CSR側</p>
<div id="client">
<p>-Vite React CSR-</p>
<p>クリック回数 {count}回</p>
<button onClick={
() => {
setCount(count + 1);
}
}>Click!!</button>
</div>
<p>------------------------------------</p>
</>
);
}
export default Csr
csr.cssは、ビルド後にcssファイルが生成されてindex.htmlに組み込まれます。
/src/csr/csr.css
中身はありませんでした。
package.json
package.jsonは、scriptsを次のように変更してあります。
package.json
{
"name": "vite-react-ssr-csr",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "set NODE_ENV=development&& node server.js",
"build": "vite build && vite build --ssr",
"start": "node server.js"
},
"dependencies": {
"react": "^19.2.3",
"react-dom": "^19.2.3"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
"@types/react": "^19.2.5",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.2",
"babel-plugin-react-compiler": "^1.0.0",
"eslint": "^9.39.1",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"express": "^5.2.1",
"globals": "^16.5.0",
"vite": "^7.3.1"
}
}
ビルドは2回行っています。
"build": "vite build && vite build --ssr",
1回目は、index.htmlを基点とした、いつものビルドです。
2回目は、--ssrフラグを指定してSSR側のビルドをおこないます。
SSR側ビルドの細かい設定は、vite.config.jsで行います。
vite.config.js
vite.config.jsは次のようになっています。
vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig(({ command, isSsrBuild }) => {
const isDev = command === "serve";
return {
plugins: [
react({
babel: {
plugins: [['babel-plugin-react-compiler']],
},
}),
],
build: isSsrBuild
? {
ssr: true,
outDir: "dist/ssr",
rollupOptions: {
input: "src/ssr/entry-ssr.jsx",
output: {
format: "esm",
},
},
}
: {
// ✅ CSR ビルド
outDir: "dist/csr",
},
ssr: isDev
? {
// 開発用
external: ["react", "react-dom"],
}
: {
// 本番用
// → react / react-dom をバンドルに含める
noExternal: [
"react",
"react-dom",
],
},
}
});
これ一つで、開発時のトランスパイルに関わる依存関係および、SSRビルド(vite build --ssr)とCSRビルド(vite build)の設定をおこなっています。
defineConfig()の引数isSsrBuild は、ビルド時に--ssrフラグが指定されているとき true です。
引数commandは、開発時は"serve"になります。
ReactでSSR/CSRを実現する際の落とし穴は、SSR側の依存関係の設定です。
SSR側の依存関係の設定
ssr: isDev
? {
// 開発用
external: ["react", "react-dom"],
}
: {
// 本番用
// → react / react-dom をバンドルに含める
noExternal: [
"react",
"react-dom",
],
},
}
ビルド時にモジュールをバンドルしません。
開発時のモジュール解決はNode.jsローダーが行います。
ssr.noExternal:
ビルド時にトランスパイル(CJSからESMへの変換等)を行いバンドルします。
また開発時は、Vite側でトランスパイルを行います。
本番時のSSRビルドは、noExternal指定でreactとreact-domをバンドルさせます。
※バンドルさせない場合は、本番サーバーにreactとreact-domをインストールする必要があります。
一方、次のように開発時にも同じ設定をおこなうと、server.jsでvite.ssrLoadModule()を呼び出したタイミングで次のようなエラーが出ます。
※今回はvite7.3.1で作成しているため、他のバージョンではエラーにならない可能性があります。
エラーになる!
ssr {
noExternal: [
"react",
"react-dom",
],
},
}
エラー内容
[vite] (ssr) Error when evaluating SSR module /src/ssr/entry-ssr.jsx: module is not defined
at eval (C:/xxx/vite-react-non-hydrated-ssr/node_modules/react/jsx-dev-runtime.js:8:3)
この問題を解決するために、開発時は、react、react-domをバンドルさせないようにexternal指定します。
※externalはデフォルトなので記述は不要ですが、ここでは明示的に記述しています。
エラーになる理由は、非表示にしました。
興味がある方は開いてください。
エラーメッセージを見ると、entry-ssr.jsxでのエラーに見えますが、node_modules/react/jsx-dev-runtime.jsでのエラーです。 このファイルはentry-ssr.jsxでJSXの<Ssr />を解決するためにインポートされます。
node_modules/react/jsx-dev-runtime.jsがこちらのコード。
/node_modules/react/jsx-dev-runtime.js
'use strict';
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react-jsx-dev-runtime.production.js');
} else {
module.exports = require('./cjs/react-jsx-dev-runtime.development.js');
}
このファイルはmodule.exportsでエクスポートしているのでCJSですね。
viteはESMなのでmodule変数は未定義となり、参照すると is not defined になります。
■external(エラーにならない)のときのviteの動作
依存関係がexternalのとき、viteはモジュールをimport()関数で読み込みます。
Node.jsはESM環境下でCJSを読み込むと、module.exportsやrequire等の問題をうまく処理してくれます。
そのため、問題のファイルを読み込んでもエラーになりません。
■noExternal(エラーになる)のときのviteの動作
一方noExternalの場合、viteはファイルの内容を読み込み、関数コードとして実行可能な形式に変換します。
そして、async関数のコンストラクタを使って関数オブジェクトを作成して実行します。
このとき、importはうまく変換できるのですが、module.exports = require()は、通常の関数呼び出しと割り当てコードとしてそのまま残ります。
その結果、module.exportsを解決できずにエラーがスローされます。
viteはCJSをESMに変換しているという情報もありますがそれがSSRにも適用されるのか、仕様なのか不具合なのかわからないのでこれ以上は深く追及しません。
テスト
次のコマンドを実行すると、viteの開発環境を使って動作テストを行えます。
npm run dev
次のメッセージが表示されたら、ブラウザでhttp://localhost:3000にアクセスしましょう。
> vite-react-ssr-csr@0.0.0 dev
> set NODE_ENV=development&& node server.js
Server running: http://localhost:3000
うまく画面が表示されたら成功です。
ビルド
ビルドは次のコマンドを実行します。
npm run build
実際には次の二つのコマンドが走っています。
vite build vite build --ssr
上がクライアント用ビルド、下がサーバー用ビルドです。
両方ビルドする必要がないときは、個別にビルドしてください。
デプロイ(サーバーへのアップロード)
サーバーで必要なファイルは、ビルドの成果物であるdistフォルダ一式と、server.jsです。
サーバー側ディレクトリ ┣━ dist ← ビルド結果 ┃ ┣━ csr ┃ ┃ ┣━ assets ┃ ┃ ┃ ┣━ index-BmbLjjn_.js ┃ ┃ ┃ ┗━ index-tn0RQdqM.css ┃ ┃ ┗━ index.html ┃ ┗━ ssr ┃ ┗━ entry-ssr.js ┗━server.js ← サーバー常駐コード
さらにexpressが必要です。
npm install express --save-dev
サーバー側のビルドでexpressをバンドルすればインストールする必要がないと思うかもしれません。
ですがexpressはserver.jsにバンドルする必要があります。
今回のビルド対象はentry-ssr.jsなので、もう一つビルド設定を追加しないといけません。
実際には、expressはNode.jsのローダーや内部APIに依存するのでバンドルせず Node.js に直接読み込ませたほうがよいでしょう。
※バンドルしたら挙動が変わる可能性があるので、めんどくさいのが本音。
ハイドレーションありSSRのコード
ハイドレーションありSSRのコードを紹介します。
https://note.affi-sapo-sv.com/demo/vite-react-hydrated-ssr/
■GitHub
https://github.com/kchan-p/vite-react-hydrated-ssr
準備
次のコマンドで、開発環境を作成します。
npm create vite@latest vite-react-hydrated-ssr -- --template react-compiler
cd vite-react-non-hydrated-ssr
npm install express --save-dev
プロジェクト名が異なるだけで、ハイドレーションなしSSRのコードの準備とほぼ同じです。
ファイル構成
ファイル構成は、ハイドレーションなしSSRのコードのファイル構成と同じですが、一部のファイルが変更されています。
vite-react-non-hydrated-ssr ┣━ dist ┃ ┣━ csr ┃ ┃ ┣━ assets ┃ ┃ ┃ ┣━ index-BmbLjjn_.js ┃ ┃ ┃ ┗━ index-tn0RQdqM.css ┃ ┃ ┗━ index.html ┃ ┗━ ssr ┃ ┗━ entry-ssr.js ┣━ public ┣━ src ┃ ┣━ assets ┃ ┣━ csr ┃ ┃ ┣━ csr.css ┃ ┃ ┣━ csr.jsx ┃ ┃ ┗━ entry-csr.jsx ←変更 ┃ ┗━ ssr ┃ ┣━ entry-ssr.jsx ←変更 ┃ ┣━ rendertohtml.js ┃ ┗━ ssr.jsx ┣━ README.md ┣━ eslint.config.js ┣━ index.html ←変更 ┣━ package-lock.json ┣━ package.json ┣━ server.js ┗━ vite.config.js
赤文字のファイル(index.html、entry-ssr.jsx、entry-csr.jsx)が変更したファイルです。
ここでは変更したファイルのみ解説します。
他のファイルは、ハイドレーションなしSSRのコードの各エントリを参照してください。
index.html
index.htmlです。
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>Vite React SSR - Hydrated SSR -</title>
</head>
<body>
<h1>Vite React SSR - Hydrated SSR -</h1>
<!--ssr-->
<script type="module" src="/src/csr/entry-csr.jsx"></script>
</body>
</html>
ハイドレーションなしSSRのコードのindex.htmlは、次のようにCSR側のエントリを含んでいましたが、今回はエントリもSSR側で生成します。
ハイドレーションなしSSRのコードのindex.html
<body>
<h1>Vite React SSR - Non-hydrated SSR -</h1>
<!--ssr-->
<div id="csr"></div>
<script type="module" src="/src/csr/entry-csr.jsx"></script>
</body>
例えば、次のようにエントリの中身をSSRで作成するパターンです。
<div id="csr"><!--ssr--></div>
entry-ssr.jsx
entry-ssr.jsxは、SSR側のコードです。
entry-ssr.jsx
import { renderToHtml } from "./rendertohtml";
import Ssr from "./ssr";
import Csr from "../csr/csr";
function htmlEncode(str) {
return String(str)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
};
const HTML_HOLDER = "<!--html-->";
async function render() {
const html = await renderToHtml(
<>
<Ssr />
{HTML_HOLDER}
<div id="csr">
<Csr />
</div>
</>
);
return html.replace(
htmlEncode(HTML_HOLDER),
`<p>------------------------------------<p>
<p>SSR側で生成したHTML</p>
<code>
${htmlEncode(html)}
</code>
<p>------------------------------------</p>`
);
}
export default render
ブラウザ上のReact管理外の部分および、エントリポイント、およびエントリポイントの中にCSR側のコンポーネント(entry-csr.jsx)を含めて、レンダーしています。
そのほかは、レンダリング結果をブラウザに表示するためにいろいろやってます。
entry-csr.jsx
entry-csr.jsxはCSR側のコンポーネントです。
entry-csr.jsx
import { hydrateRoot } from 'react-dom/client'
import './csr.css'
import Csr from './csr.jsx'
const container = document.getElementById("csr");
if (container) {
hydrateRoot(container,<Csr />);
}
hydrateRoot()は、htmlとして受け取ったエントリポイント内の内容を初期状態として利用します。
ただし、その内容と、起動後のコンポーネントのレンダリング結果が異なる「hydrate mismatch」エラーになります。
そのため、SSR側とCSR側で同じコンポーネントを利用するのが望ましいです。
その他の情報
その他の情報はハイドレーションなしSSRのコードと同じですので、各エントリを参照してください。
コードのダウンロード/デモ
ハイドレーションなしSSR
https://note.affi-sapo-sv.com/demo/vite-react-non-hydrated-ssr/
■ダウンロード(GitHub)
https://github.com/kchan-p/vite-react-non-hydrated-ssr
ハイドレーションあり
https://note.affi-sapo-sv.com/demo/vite-react-hydrated-ssr/
■ダウンロード(GitHub)
https://github.com/kchan-p/vite-react-hydrated-ssr
更新日:2026/01/29
関連記事
スポンサーリンク
記事の内容について

こんにちはけーちゃんです。
説明するのって難しいですね。
「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。
裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。
掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。
ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php
このサイトは、リンクフリーです。大歓迎です。

