阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 服务端渲染优化要点

服务端渲染优化要点

作者:陈川 阅读数:42096人阅读 分类: 构建工具

服务端渲染优化要点

服务端渲染(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

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌