React 服务端渲染核心原理与实践
客户端渲染与服务端渲染对比
客户端渲染(CSR)流程:
- 服务器返回空白HTML文档
- 浏览器下载JavaScript资源
- 浏览器执行React应用逻辑
- 最终完成页面渲染
服务端渲染(SSR)流程:
- 服务器直接生成完整的HTML文档
- 浏览器接收并显示预渲染内容
关键优势分析:
- SSR提供更快的首屏加载体验,用户无需等待JavaScript下载执行
- 搜索引擎能够直接解析HTML内容,提升SEO效果
服务端Webpack环境搭建
首先在项目根目录创建服务端入口文件 server/entry.js:
const express = require('express');
const app = express();
app.get('/', (request, response) => {
response.send(`
<html>
<head>
<title>React Server-Side Rendering</title>
</head>
<body>
<div id="app"><h1>React SSR Implementation</h1></div>
</body>
</html>
`);
});
const serverInstance = app.listen(8080);
更新 package.json 启动脚本:
"scripts": {
"start": "node ./server/entry.js"
}
执行 npm run start 即可在8080端口访问应用。
为服务端代码集成Webpack构建,首先安装必要依赖:
npm install webpack webpack-cli babel-loader @babel/core @babel/preset-env @babel/preset-react @babel/preset-stage-0 webpack-node-externals
创建 webpack.server.config.js 配置:
const path = require('path');
const nodeModulesExternal = require('webpack-node-externals');
module.exports = {
target: 'node',
mode: 'development',
entry: './server/entry.js',
output: {
filename: 'server-bundle.js',
path: path.resolve(__dirname, 'dist')
},
externals: [nodeModulesExternal()],
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader',
exclude: /node_modules/,
options: {
presets: [
'@babel/react',
'@babel/stage-0',
['@babel/env', {
targets: {
browsers: ['last 2 versions']
}
}]
]
}
}
]
}
};
修改启动脚本指向构建后的文件:
"start": "node ./dist/server-bundle.js",
"build-server": "webpack --config webpack.server.config.js"
组件服务端渲染实现
创建示例组件 components/main.jsx:
import React from 'react';
const MainComponent = () => (
<div className="main-content">
<h2>Welcome to SSR</h2>
<p>This content was rendered on server side.</p>
</div>
);
export default MainComponent;
更新服务端入口文件,集成React组件渲染:
const express = require('express');
import React from 'react';
import { renderToString } from 'react-dom/server';
import MainContent from './components/main';
const app = express();
app.get('/', (req, res) => {
const componentHtml = renderToString(<MainContent />);
res.send(`
<html>
<head>
<title>SSR Application</title>
</head>
<body>
<div id="root">${componentHtml}</div>
</body>
</html>
`);
});
const serverInstance = app.listen(8080);
执行构建命令后,访问页面将看到服务端渲染的组件内容,查看页面源码可验证组件已预渲染。
同构应用开发
由于服务端渲染不包含事件绑定,需要在客户端重新激活交互功能。创建客户端入口 client/app.js:
import React from 'react';
import { hydrate } from 'react-dom';
import MainContent from '../components/main';
hydrate(<MainContent />, document.getElementById('root'));
配置静态资源中间件:
app.use(express.static('public'));
创建客户端Webpack配置 webpack.client.config.js:
const path = require('path');
module.exports = {
mode: 'development',
entry: './client/app.js',
output: {
filename: 'client-app.js',
path: path.resolve(__dirname, 'public')
},
module: {
rules: [
{
test: /\.jsx?$/,
use: 'babel-loader',
exclude: /node_modules/,
options: {
presets: [
'@babel/react',
'@babel/stage-0',
['@babel/env', {
targets: {
browsers: ['last 2 versions']
}
}]
]
}
}
]
}
};
在HTML模板中引入客户端脚本:
<script src="/client-app.js"></script>
使用 hydrate 方法而非 render 可避免重复渲染警告,实现服务端内容的客户端激活。
构建配置优化
采用配置合并策略优化构建流程,安装 webpack-merge:
创建基础配置 webpack.common.config.js:
module.exports = {
module: {
rules: [
{
test: /\.jsx?$/,
use: 'babel-loader',
exclude: /node_modules/,
options: {
presets: [
'@babel/react',
'@babel/stage-0',
['@babel/env', {
targets: {
browsers: ['last 2 versions']
}
}]
]
}
}
]
}
};
客户端配置继承基础配置:
const path = require('path');
const { merge } = require('webpack-merge');
const baseConfig = require('./webpack.common.config');
const clientSpecific = {
mode: 'development',
entry: './client/app.js',
output: {
filename: 'client-bundle.js',
path: path.resolve(__dirname, 'public')
}
};
module.exports = merge(baseConfig, clientSpecific);
服务端配置同样继承:
const path = require('path');
const nodeModulesExternal = require('webpack-node-externals');
const { merge } = require('webpack-merge');
const baseConfig = require('./webpack.common.config');
const serverSpecific = {
target: 'node',
mode: 'development',
entry: './server/entry.js',
output: {
filename: 'server-bundle.js',
path: path.resolve(__dirname, 'dist')
},
externals: [nodeModulesExternal()]
};
module.exports = merge(baseConfig, serverSpecific);
完整SSR流程总结:服务端执行React渲染生成HTML → 发送至浏览器 → 浏览器展示预渲染内容 → 加载客户端JavaScript → 客户端React接管并激活交互功能。