服务端渲染优化要点
服务端渲染优化要点
服务端渲染(SSR)能提升首屏加载速度与SEO效果,但实现不当可能导致性能瓶颈。以下是基于Webpack的SSR优化关键点与实践方案。
代码分割与异步加载
服务端渲染需避免单文件过大,通过动态导入减少初始负载。Webpack的import()
语法配合@loadable/component
可实现组件级代码分割:
// 使用loadable-components实现异步加载
import loadable from '@loadable/component';
const AsyncComponent = loadable(() => import('./HeavyComponent'));
function App() {
return (
<div>
<AsyncComponent fallback={<div>Loading...</div>} />
</div>
);
}
服务端需使用@loadable/server
收集分块信息:
import { ChunkExtractor } from '@loadable/server';
const extractor = new ChunkExtractor({ statsFile: './build/loadable-stats.json' });
const jsx = extractor.collectChunks(<App />);
const scripts = extractor.getScriptTags(); // 插入到HTML模板
内存缓存策略
高频访问页面可采用内存缓存,避免重复渲染。使用LRU缓存方案:
const LRU = require('lru-cache');
const ssrCache = new LRU({
max: 100, // 最大缓存数
maxAge: 1000 * 60 * 15 // 15分钟
});
function renderWithCache(req, res, pagePath) {
const key = req.url;
if (ssrCache.has(key)) {
return res.send(ssrCache.get(key));
}
renderToString(pagePath).then(html => {
ssrCache.set(key, html);
res.send(html);
});
}
注意缓存需根据数据变化及时失效,可通过版本号或时间戳管理:
// 带数据版本号的缓存键
const dataVersion = getDataVersion();
const cacheKey = `${req.url}_${dataVersion}`;
构建配置优化
Webpack需区分客户端与服务端配置:
// webpack.server.js
module.exports = {
target: 'node',
entry: './server/render.js',
output: {
filename: 'server-bundle.js',
libraryTarget: 'commonjs2'
},
externals: [nodeExternals()] // 排除node_modules
};
// webpack.client.js
module.exports = {
entry: './src/client.js',
plugins: [
new MiniCssExtractPlugin() // 提取CSS
]
};
使用webpack-node-externals
避免打包Node模块,服务端构建开启optimization: { minimize: false }
可提升构建速度。
流式渲染与性能监控
大体积页面采用流式渲染减少TTFB时间:
import { renderToNodeStream } from 'react-dom/server';
app.get('*', (req, res) => {
const stream = renderToNodeStream(<App />);
res.write('<!DOCTYPE html><html><head><title>SSR</title></head><body><div id="root">');
stream.pipe(res, { end: false });
stream.on('end', () => {
res.write('</div></body></html>');
res.end();
});
});
集成性能监控收集关键指标:
const { performance, PerformanceObserver } = require('perf_hooks');
const obs = new PerformanceObserver((list) => {
const entry = list.getEntries()[0];
console.log(`Render time: ${entry.duration}ms`);
});
obs.observe({ entryTypes: ['measure'] });
performance.mark('render-start');
renderToString(App).then(() => {
performance.mark('render-end');
performance.measure('SSR Render', 'render-start', 'render-end');
});
数据预取与状态同步
服务端数据预取避免客户端二次请求:
// 定义静态方法获取数据
class PostPage extends React.Component {
static async getInitialProps({ req }) {
const res = await fetch(`https://api.example.com/post/${req.params.id}`);
return { post: await res.json() };
}
}
// 服务端渲染时调用
const initialProps = await PostPage.getInitialProps(context);
const html = renderToString(<PostPage {...initialProps} />);
使用window.__INITIAL_STATE__
同步状态到客户端:
<script>
window.__INITIAL_STATE__ = ${JSON.stringify(initialProps)};
</script>
客户端初始化时复用数据:
const initialData = window.__INITIAL_STATE__ || {};
hydrateRoot(<App {...initialData} />, document.getElementById('root'));
静态资源优化
CSS内联减少请求数:
const css = fs.readFileSync(path.join(__dirname, '../dist/main.css'), 'utf8');
const styleTag = `<style>${css}</style>`;
使用helmet
管理头部标签:
import Helmet from 'react-helmet';
const head = Helmet.renderStatic();
const html = `
<!doctype html>
<html ${head.htmlAttributes.toString()}>
<head>
${head.title.toString()}
${head.meta.toString()}
${head.link.toString()}
</head>
<body>
<div id="root">${content}</div>
</body>
</html>
`;
错误处理与降级方案
实现服务端渲染错误边界:
class ErrorBoundary extends React.Component {
componentDidCatch(error) {
console.error('SSR Error:', error);
}
render() {
return this.props.children;
}
}
const html = renderToString(
<ErrorBoundary>
<App />
</ErrorBoundary>
);
降级为客户端渲染的兜底策略:
try {
renderToString(App);
} catch (e) {
logger.error('SSR Failed', e);
res.send(`
<div id="root"></div>
<script>console.error('SSR Error:', ${JSON.stringify(e.message)})</script>
`);
}
编译时优化
使用babel-plugin-transform-react-remove-prop-types
移除PropTypes:
// .babelrc
{
"env": {
"production": {
"plugins": ["transform-react-remove-prop-types"]
}
}
}
通过DefinePlugin
注入环境变量:
new webpack.DefinePlugin({
'process.env.SSR': JSON.stringify(true)
})
组件中区分运行环境:
function ImageComponent({ src }) {
return process.env.SSR ?
<img src="placeholder.jpg" data-src={src} /> :
<LazyImage src={src} />;
}
服务端特定配置
调整Node.js服务器参数:
// 增加HTTP服务器超时时间
server.timeout = 60000;
// 启用keep-alive
server.keepAliveTimeout = 30000;
使用compression
中间件开启Gzip:
const compression = require('compression');
app.use(compression({ threshold: 0 }));
持续集成优化
构建阶段生成分析报告:
// webpack.config.js
module.exports = {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
reportFilename: '../bundle-analysis.html'
})
]
};
Docker镜像构建时分层缓存:
# 先安装依赖
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
# 再拷贝源码
COPY . .
RUN yarn build:ssr
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn