服务端渲染 #
一、SSR 基础 #
1.1 为什么需要 SSR 样式处理 #
text
SSR 样式问题
┌─────────────────────────────────────────────────────────────┐
│ │
│ 客户端渲染 (CSR) │
│ ├── HTML 先加载 │
│ ├── JavaScript 执行 │
│ └── 样式后注入 → FOUC (闪烁) │
│ │
│ 服务端渲染 (SSR) │
│ ├── HTML 包含样式 │
│ ├── 首屏即渲染完整样式 │
│ └── 无闪烁,更好的 SEO │
│ │
└─────────────────────────────────────────────────────────────┘
1.2 ServerStyleSheet #
jsx
import { renderToString } from 'react-dom/server';
import { ServerStyleSheet } from 'styled-components';
function renderApp() {
const sheet = new ServerStyleSheet();
try {
const html = renderToString(
sheet.collectStyles(<App />)
);
const styleTags = sheet.getStyleTags();
return `
<!DOCTYPE html>
<html>
<head>
${styleTags}
</head>
<body>
<div id="root">${html}</div>
<script src="/client.js"></script>
</body>
</html>
`;
} finally {
sheet.seal();
}
}
二、Next.js 集成 #
2.1 Next.js 配置 #
javascript
const nextConfig = {
compiler: {
styledComponents: true,
},
};
module.exports = nextConfig;
2.2 App Router 配置 #
tsx
import React from 'react';
import { ServerStyleSheet, StyleSheetManager } from 'styled-components';
import { useServerInsertedHTML } from 'next/navigation';
export default function StyledComponentsRegistry({
children,
}: {
children: React.ReactNode;
}) {
const [styledComponentsStyleSheet] = React.useState(
() => new ServerStyleSheet()
);
useServerInsertedHTML(() => {
const styles = styledComponentsStyleSheet.getStyleElement();
styledComponentsStyleSheet.instance.clearTag();
return <>{styles}</>;
});
if (typeof window !== 'undefined') {
return <>{children}</>;
}
return (
<StyleSheetManager sheet={styledComponentsStyleSheet.instance}>
{children}
</StyleSheetManager>
);
}
2.3 Layout 配置 #
tsx
import StyledComponentsRegistry from './lib/registry';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<StyledComponentsRegistry>
{children}
</StyledComponentsRegistry>
</body>
</html>
);
}
三、自定义 SSR 配置 #
3.1 Express 集成 #
jsx
import express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';
import { ServerStyleSheet, StyleSheetManager } from 'styled-components';
import App from './App';
const app = express();
app.get('*', (req, res) => {
const sheet = new ServerStyleSheet();
try {
const html = renderToString(
<StyleSheetManager sheet={sheet.instance}>
<App />
</StyleSheetManager>
);
const styleTags = sheet.getStyleTags();
const document = `
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
${styleTags}
</head>
<body>
<div id="root">${html}</div>
<script src="/client.js"></script>
</body>
</html>
`;
res.send(document);
} catch (error) {
console.error(error);
res.status(500).send('Error');
} finally {
sheet.seal();
}
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
3.2 Koa 集成 #
jsx
import Koa from 'koa';
import { renderToString } from 'react-dom/server';
import { ServerStyleSheet, StyleSheetManager } from 'styled-components';
import App from './App';
const app = new Koa();
app.use(async (ctx) => {
const sheet = new ServerStyleSheet();
try {
const html = renderToString(
<StyleSheetManager sheet={sheet.instance}>
<App />
</StyleSheetManager>
);
const styleTags = sheet.getStyleTags();
ctx.body = `
<!DOCTYPE html>
<html>
<head>
${styleTags}
</head>
<body>
<div id="root">${html}</div>
<script src="/client.js"></script>
</body>
</html>
`;
} finally {
sheet.seal();
}
});
app.listen(3000);
四、样式提取 #
4.1 getStyleTags #
jsx
import { ServerStyleSheet } from 'styled-components';
const sheet = new ServerStyleSheet();
const styleTags = sheet.getStyleTags();
console.log(styleTags);
sheet.seal();
输出示例:
html
<style data-styled="true" data-styled-version="6.0.0">
.sc-aXZVg{padding:12px 24px;}
.sc-bZQynM{background:#667eea;color:white;}
</style>
4.2 getStyleElement #
jsx
import { ServerStyleSheet } from 'styled-components';
const sheet = new ServerStyleSheet();
const styleElements = sheet.getStyleElement();
console.log(styleElements);
sheet.seal();
4.3 收集所有样式 #
jsx
import { renderToString } from 'react-dom/server';
import { ServerStyleSheet, StyleSheetManager } from 'styled-components';
function collectStyles(app) {
const sheet = new ServerStyleSheet();
try {
const html = renderToString(
<StyleSheetManager sheet={sheet.instance}>
{app}
</StyleSheetManager>
);
return {
html,
styles: sheet.getStyleTags(),
};
} finally {
sheet.seal();
}
}
const { html, styles } = collectStyles(<App />);
五、流式渲染 #
5.1 renderToNodeStream #
jsx
import { renderToNodeStream } from 'react-dom/server';
import { ServerStyleSheet, StyleSheetManager } from 'styled-components';
import express from 'express';
const app = express();
app.get('*', (req, res) => {
const sheet = new ServerStyleSheet();
res.write(`
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
`);
const stream = sheet.interleaveWithNodeStream(
renderToNodeStream(
<StyleSheetManager sheet={sheet.instance}>
<App />
</StyleSheetManager>
)
);
stream.pipe(res, { end: false });
stream.on('end', () => {
res.write(`
</head>
<body>
<script src="/client.js"></script>
</body>
</html>
`);
res.end();
sheet.seal();
});
});
5.2 renderToPipeableStream #
jsx
import { renderToPipeableStream } from 'react-dom/server';
import { ServerStyleSheet, StyleSheetManager } from 'styled-components';
function renderApp(res) {
const sheet = new ServerStyleSheet();
const { pipe } = renderToPipeableStream(
<StyleSheetManager sheet={sheet.instance}>
<App />
</StyleSheetManager>,
{
onShellReady() {
res.setHeader('Content-Type', 'text/html');
pipe(res);
sheet.seal();
},
onShellError(error) {
res.status(500).send('Error');
sheet.seal();
},
}
);
}
六、客户端水合 #
6.1 hydrateRoot #
jsx
import { hydrateRoot } from 'react-dom/client';
import { StyleSheetManager } from 'styled-components';
import App from './App';
hydrateRoot(
document.getElementById('root'),
<StyleSheetManager>
<App />
</StyleSheetManager>
);
6.2 水合警告处理 #
jsx
import { hydrateRoot } from 'react-dom/client';
import { StyleSheetManager } from 'styled-components';
const sheet = new ServerStyleSheet();
hydrateRoot(
document.getElementById('root'),
<StyleSheetManager sheet={sheet.instance}>
<App />
</StyleSheetManager>,
{
onRecoverableError(error) {
console.warn('Hydration error:', error);
},
}
);
七、性能优化 #
7.1 样式缓存 #
jsx
import { ServerStyleSheet } from 'styled-components';
const styleCache = new Map();
function getCachedStyles(cacheKey, renderFn) {
if (styleCache.has(cacheKey)) {
return styleCache.get(cacheKey);
}
const sheet = new ServerStyleSheet();
try {
const result = renderFn(sheet);
styleCache.set(cacheKey, result);
return result;
} finally {
sheet.seal();
}
}
7.2 并行渲染 #
jsx
import { renderToString } from 'react-dom/server';
import { ServerStyleSheet } from 'styled-components';
async function renderParallel(components) {
const results = await Promise.all(
components.map(async (Component) => {
const sheet = new ServerStyleSheet();
try {
const html = renderToString(
<StyleSheetManager sheet={sheet.instance}>
<Component />
</StyleSheetManager>
);
return {
html,
styles: sheet.getStyleTags(),
};
} finally {
sheet.seal();
}
})
);
return results;
}
7.3 样式去重 #
jsx
import { ServerStyleSheet } from 'styled-components';
function deduplicateStyles(styleTags) {
const seen = new Set();
const unique = [];
const regex = /<style[^>]*>([\s\S]*?)<\/style>/g;
let match;
while ((match = regex.exec(styleTags)) !== null) {
const content = match[1];
if (!seen.has(content)) {
seen.add(content);
unique.push(match[0]);
}
}
return unique.join('\n');
}
八、常见问题 #
8.1 样式闪烁 #
问题:页面加载时出现样式闪烁
解决方案:
jsx
import { ServerStyleSheet } from 'styled-components';
const sheet = new ServerStyleSheet();
const html = renderToString(
<StyleSheetManager sheet={sheet.instance}>
<App />
</StyleSheetManager>
);
const styles = sheet.getStyleTags();
const document = `
<!DOCTYPE html>
<html>
<head>
${styles}
</head>
<body>
<div id="root">${html}</div>
</body>
</html>
`;
sheet.seal();
8.2 类名不匹配 #
问题:服务端和客户端类名不一致
解决方案:
javascript
const nextConfig = {
compiler: {
styledComponents: {
ssr: true,
displayName: true,
preprocess: true,
},
},
};
8.3 内存泄漏 #
问题:ServerStyleSheet 未正确清理
解决方案:
jsx
const sheet = new ServerStyleSheet();
try {
const html = renderToString(
<StyleSheetManager sheet={sheet.instance}>
<App />
</StyleSheetManager>
);
return { html, styles: sheet.getStyleTags() };
} finally {
sheet.seal();
}
九、调试技巧 #
9.1 样式检查 #
jsx
import { ServerStyleSheet } from 'styled-components';
const sheet = new ServerStyleSheet();
const html = renderToString(
<StyleSheetManager sheet={sheet.instance}>
<App />
</StyleSheetManager>
);
console.log('Generated styles:', sheet.getStyleTags());
console.log('Generated HTML:', html);
sheet.seal();
9.2 样式统计 #
jsx
function analyzeStyles(styleTags) {
const regex = /\.sc-[a-zA-Z0-9]+/g;
const matches = styleTags.match(regex) || [];
return {
totalClasses: matches.length,
uniqueClasses: new Set(matches).size,
};
}
十、总结 #
SSR 要点速查表:
| 要点 | 说明 |
|---|---|
| ServerStyleSheet | 收集服务端样式 |
| StyleSheetManager | 管理样式表 |
| getStyleTags | 获取样式标签 |
| getStyleElement | 获取样式元素 |
| seal | 清理资源 |
| hydrateRoot | 客户端水合 |
下一步:学习 工具生态 掌握开发工具和插件。
最后更新:2026-03-28