阿里云主机折上折
  • 微信号
Current Site:Index > Key points for server-side rendering optimization

Key points for server-side rendering optimization

Author:Chuan Chen 阅读数:42095人阅读 分类: 构建工具

Key Points for Server-Side Rendering Optimization

Server-Side Rendering (SSR) improves first-screen loading speed and SEO effectiveness, but improper implementation can lead to performance bottlenecks. Below are key optimization points and practical solutions based on Webpack.

Code Splitting and Async Loading

SSR should avoid excessively large single files by using dynamic imports to reduce initial load. Webpack's import() syntax combined with @loadable/component enables component-level code splitting:

// Implement async loading with loadable-components
import loadable from '@loadable/component';
const AsyncComponent = loadable(() => import('./HeavyComponent'));

function App() {
  return (
    <div>
      <AsyncComponent fallback={<div>Loading...</div>} />
    </div>
  );
}

The server side needs to use @loadable/server to collect chunk information:

import { ChunkExtractor } from '@loadable/server';

const extractor = new ChunkExtractor({ statsFile: './build/loadable-stats.json' });
const jsx = extractor.collectChunks(<App />);
const scripts = extractor.getScriptTags(); // Insert into HTML template

Memory Caching Strategy

High-traffic pages can use in-memory caching to avoid repeated rendering. Implement an LRU caching solution:

const LRU = require('lru-cache');
const ssrCache = new LRU({
  max: 100, // Maximum cache entries
  maxAge: 1000 * 60 * 15 // 15 minutes
});

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);
  });
}

Note that caches must be invalidated promptly when data changes, managed via version numbers or timestamps:

// Cache key with data version
const dataVersion = getDataVersion();
const cacheKey = `${req.url}_${dataVersion}`;

Build Configuration Optimization

Webpack requires separate configurations for client and server:

// webpack.server.js
module.exports = {
  target: 'node',
  entry: './server/render.js',
  output: {
    filename: 'server-bundle.js',
    libraryTarget: 'commonjs2'
  },
  externals: [nodeExternals()] // Exclude node_modules
};

// webpack.client.js
module.exports = {
  entry: './src/client.js',
  plugins: [
    new MiniCssExtractPlugin() // Extract CSS
  ]
};

Use webpack-node-externals to avoid bundling Node modules. For server builds, enabling optimization: { minimize: false } can improve build speed.

Streaming Rendering and Performance Monitoring

For large pages, use streaming rendering to reduce TTFB time:

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();
  });
});

Integrate performance monitoring to collect key metrics:

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');
});

Data Prefetching and State Synchronization

Prefetch data on the server to avoid client-side duplicate requests:

// Define static method to fetch data
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() };
  }
}

// Call during SSR
const initialProps = await PostPage.getInitialProps(context);
const html = renderToString(<PostPage {...initialProps} />);

Use window.__INITIAL_STATE__ to sync state to the client:

<script>
  window.__INITIAL_STATE__ = ${JSON.stringify(initialProps)};
</script>

Reuse data during client initialization:

const initialData = window.__INITIAL_STATE__ || {};
hydrateRoot(<App {...initialData} />, document.getElementById('root'));

Static Resource Optimization

Inline CSS to reduce requests:

const css = fs.readFileSync(path.join(__dirname, '../dist/main.css'), 'utf8');
const styleTag = `<style>${css}</style>`;

Use helmet to manage head tags:

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>
`;

Error Handling and Fallback Solutions

Implement error boundaries for SSR:

class ErrorBoundary extends React.Component {
  componentDidCatch(error) {
    console.error('SSR Error:', error);
  }
  render() {
    return this.props.children;
  }
}

const html = renderToString(
  <ErrorBoundary>
    <App />
  </ErrorBoundary>
);

Fallback to client-side rendering as a backup:

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>
  `);
}

Compile-Time Optimization

Use babel-plugin-transform-react-remove-prop-types to remove PropTypes:

// .babelrc
{
  "env": {
    "production": {
      "plugins": ["transform-react-remove-prop-types"]
    }
  }
}

Inject environment variables via DefinePlugin:

new webpack.DefinePlugin({
  'process.env.SSR': JSON.stringify(true)
})

Differentiate environments in components:

function ImageComponent({ src }) {
  return process.env.SSR ? 
    <img src="placeholder.jpg" data-src={src} /> : 
    <LazyImage src={src} />;
}

Server-Specific Configuration

Adjust Node.js server parameters:

// Increase HTTP server timeout
server.timeout = 60000;

// Enable keep-alive
server.keepAliveTimeout = 30000;

Enable Gzip with the compression middleware:

const compression = require('compression');
app.use(compression({ threshold: 0 }));

Continuous Integration Optimization

Generate analysis reports during the build phase:

// webpack.config.js
module.exports = {
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'static',
      reportFilename: '../bundle-analysis.html'
    })
  ]
};

Use layered caching in Docker image builds:

# Install dependencies first
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile

# Copy source code next
COPY . .
RUN yarn build:ssr

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.