Server-side rendering (SSR) optimization strategies
Server-Side Rendering (SSR) Optimization Strategies
Server-Side Rendering (SSR) can improve first-screen loading speed and enhance SEO effectiveness, but improper implementation may lead to excessive server load or inefficient rendering. Through reasonable caching strategies, code splitting, streaming, and other technical approaches, the performance of SSR applications can be significantly improved.
Static Content Caching
For page content that does not change frequently, use in-memory caching or distributed caching to store rendered results. Frameworks like Next.js have built-in automatic static optimization features, which can be implemented via getStaticProps
:
export async function getStaticProps() {
const res = await fetch('https://api.example.com/data')
const data = await res.json()
return {
props: { data },
revalidate: 3600 // Regenerate every hour
}
}
Redis caching example:
async function renderWithCache(req, res) {
const cacheKey = req.url;
const cachedHtml = await redis.get(cacheKey);
if (cachedHtml) {
return res.send(cachedHtml);
}
const html = await renderToString(<App />);
await redis.setex(cacheKey, 3600, html); // Cache for 1 hour
res.send(html);
}
Component-Level Caching
Implement fine-grained caching strategies for partially cacheable components. React's react-ssr-prepass
can prefetch data:
import { prepass } from 'react-ssr-prepass';
async function renderApp() {
const cache = new Map();
await prepass(<App />, (element) => {
if (element.type?.fetchData) {
const promise = element.type.fetchData();
cache.set(element.type, promise);
}
});
const data = await Promise.all([...cache.values()]);
const html = renderToString(<App />);
return { html, data };
}
Streaming Rendering
Use renderToNodeStream
instead of renderToString
for progressive transmission:
import { renderToNodeStream } from 'react-dom/server';
app.get('/stream', (req, res) => {
res.write('<!DOCTYPE html><html><head><title>Stream</title></head><body><div id="root">');
const stream = renderToNodeStream(<App />);
stream.pipe(res, { end: false });
stream.on('end', () => {
res.write('</div></body></html>');
res.end();
});
});
Code Splitting and Lazy Loading
Combine dynamic import()
for on-demand loading:
// Server-side configuration
import { ChunkExtractor } from '@loadable/server';
const statsFile = path.resolve('../dist/loadable-stats.json');
const extractor = new ChunkExtractor({ statsFile });
function renderApp() {
const jsx = extractor.collectChunks(<App />);
const html = renderToString(jsx);
const scripts = extractor.getScriptTags();
return { html, scripts };
}
Client-side configuration:
import { loadableReady } from '@loadable/component';
loadableReady(() => {
hydrateRoot(document.getElementById('root'), <App />);
});
Data Prefetching Optimization
Adopt parallel data fetching strategies to reduce request waterfalls:
async function fetchAllData(components) {
const promises = components
.map(comp => comp.fetchData?.())
.filter(Boolean);
const results = await Promise.allSettled(promises);
return results.map(result =>
result.status === 'fulfilled' ? result.value : null
);
}
Memory Management
Global state handling to avoid memory leaks:
function createAppState() {
return {
user: null,
theme: 'light',
// Other states...
};
}
app.get('/', (req, res) => {
const appState = createAppState();
const html = renderToString(
<AppContext.Provider value={appState}>
<App />
</AppContext.Provider>
);
// Ensure state cleanup after each request
appState = null;
});
Server-Side Performance Monitoring
Implement rendering performance tracking:
app.use((req, res, next) => {
const start = process.hrtime();
res.on('finish', () => {
const duration = process.hrtime(start);
const ms = duration[0] * 1000 + duration[1] / 1e6;
metrics.trackRenderTime(req.path, ms);
if (ms > 500) {
logger.warn(`Slow render detected: ${req.path} took ${ms.toFixed(2)}ms`);
}
});
next();
});
Build Optimization
Configure Webpack for SSR-specific builds:
module.exports = {
target: 'node',
output: {
libraryTarget: 'commonjs2'
},
externals: [nodeExternals()],
module: {
rules: [
{
test: /\.css$/,
use: 'null-loader' // Ignore CSS files
}
]
}
};
Error Handling
Ensure single component errors do not affect overall rendering:
function SafeComponent({ children }) {
try {
return children();
} catch (error) {
console.error('Component render error:', error);
return null;
}
}
// Usage
<SafeComponent>
{() => <UnstableComponent />}
</SafeComponent>
Hybrid Rendering Strategy
Dynamically select rendering mode based on routes:
app.get('*', async (req, res) => {
const isBot = detectBot(req.headers['user-agent']);
const needsSEO = ['/about', '/products'].includes(req.path);
if (isBot || needsSEO) {
// Full SSR
const html = await renderToString(<App />);
res.send(html);
} else {
// Client-side rendering
res.sendFile(path.join(__dirname, 'static/index.html'));
}
});
Server-Side Resource Compression
Enable efficient compression middleware:
const compression = require('compression');
const zlib = require('zlib');
app.use(compression({
level: zlib.constants.Z_BEST_COMPRESSION,
threshold: '1kb',
filter: (req) => !req.path.endsWith('.jpg') // Exclude already compressed resources
}));
Template Precompilation
Precompile frequently used templates:
const templateCache = new Map();
function compileTemplate(templateName) {
if (templateCache.has(templateName)) {
return templateCache.get(templateName);
}
const template = Handlebars.compile(fs.readFileSync(`templates/${templateName}.hbs`));
templateCache.set(templateName, template);
return template;
}
Database Query Optimization
Reduce database queries during rendering:
async function getInitialData() {
// Use a single complex query instead of multiple simple queries
const [user, products, notifications] = await Promise.all([
User.findOne({ id: 123 }),
Product.find({ featured: true }),
Notification.count({ unread: true })
]);
return { user, products, notifications };
}
SSR Fallback Mechanism
Automatically fall back to CSR when SSR fails:
async function renderApp() {
try {
const html = await renderToString(<App />);
return { html, isServerRendered: true };
} catch (error) {
console.error('SSR failed, falling back to CSR:', error);
return { html: '', isServerRendered: false };
}
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn