Key points for server-side rendering optimization
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