SSR服务端渲染完全指南
📋 概述
**SSR(Server-Side Rendering)**是将ECharts图表在服务端生成图片或SVG,然后发送到客户端的技术。它解决了SEO、首屏加载速度、社交媒体分享等问题,是现代化Web应用的重要组成部分。
核心价值
- SEO友好:搜索引擎可以索引图表内容
- 首屏加速:用户立即看到完整页面
- 社交分享:Open Graph图片正常显示
- 性能优化:减少客户端计算压力
- 降级方案:JavaScript禁用时仍可显示
🎯 核心概念
1. SSR方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Node.js + Canvas | 灵活可控 | 需要服务器 | 动态图表 |
| Puppeteer | 真实浏览器环境 | 资源占用大 | 复杂交互 |
| 预渲染静态图片 | 简单快速 | 不灵活 | 固定图表 |
| 客户端 hydration | 完整交互 | 首次无图表 | 数据大屏 |
🔧 Next.js实现
1. API Route生成图片
typescript
// pages/api/chart.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { createCanvas } from 'canvas';
import * as echarts from 'echarts';
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const { option } = req.body;
// 创建Canvas
const canvas = createCanvas(800, 600);
// 初始化ECharts
const chart = echarts.init(canvas as any);
chart.setOption(JSON.parse(option));
// 导出为PNG
const buffer = canvas.toBuffer('image/png');
res.setHeader('Content-Type', 'image/png');
res.send(buffer);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2. 页面中使用
tsx
// pages/dashboard.tsx
import { useState, useEffect } from 'react';
export default function Dashboard() {
const [chartImage, setChartImage] = useState<string>('');
useEffect(() => {
// 客户端获取图片
fetch('/api/chart', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
option: JSON.stringify({
xAxis: { type: 'category', data: ['A', 'B', 'C'] },
yAxis: { type: 'value' },
series: [{ type: 'bar', data: [10, 20, 30] }]
})
})
})
.then(res => res.blob())
.then(blob => {
setChartImage(URL.createObjectURL(blob));
});
}, []);
return (
<div>
{chartImage ? (
<img src={chartImage} alt="图表" />
) : (
<div>加载中...</div>
)}
</div>
);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
💡 Puppeteer方案
typescript
// scripts/render-chart.ts
import puppeteer from 'puppeteer';
import * as echarts from 'echarts';
async function renderChart(option: any, outputPath: string) {
const browser = await puppeteer.launch();
const page = await browser.newPage();
// 设置视口
await page.setViewport({ width: 800, height: 600 });
// 注入ECharts
await page.addScriptTag({
path: require.resolve('echarts/dist/echarts.min.js')
});
// 渲染图表
await page.evaluate((opt) => {
const container = document.createElement('div');
container.style.width = '800px';
container.style.height = '600px';
document.body.appendChild(container);
const chart = (window as any).echarts.init(container);
chart.setOption(opt);
}, option);
// 等待渲染
await page.waitForTimeout(500);
// 截图
await page.screenshot({ path: outputPath });
await browser.close();
}
renderChart({
xAxis: { type: 'category', data: ['A', 'B', 'C'] },
yAxis: { type: 'value' },
series: [{ type: 'bar', data: [10, 20, 30] }]
}, 'chart.png');1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
🎯 最佳实践
1. 缓存策略
typescript
import NodeCache from 'node-cache';
const cache = new NodeCache({ stdTTL: 3600 }); // 1小时
async function getCachedChart(optionHash: string, option: any) {
const cached = cache.get(optionHash);
if (cached) {
return cached;
}
const image = await generateChart(option);
cache.set(optionHash, image);
return image;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2. 懒加载Hydration
tsx
// 服务端显示图片,客户端替换为交互式图表
function HybridChart({ option }: { option: any }) {
const [hydrated, setHydrated] = useState(false);
useEffect(() => {
setHydrated(true);
}, []);
if (!hydrated) {
// 服务端:显示静态图片
return <img src={`/api/chart-static?hash=${hash(option)}`} />;
}
// 客户端:渲染交互式图表
return <InteractiveChart option={option} />;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
📊 性能指标
| 方案 | 生成时间 | 文件大小 | 适用场景 |
|---|---|---|---|
| Canvas | 100-300ms | 50-200KB | 简单图表 |
| Puppeteer | 500-1000ms | 100-500KB | 复杂图表 |
| 预渲染 | 0ms | 50-200KB | 固定图表 |
🔗 相关链接
- React Hook封装
- 性能优化
💎 总结
SSR核心价值:
- ✅ SEO友好,可被索引
- ✅ 首屏加载快
- ✅ 社交分享友好
- ✅ 降级方案完善
掌握SSR,让图表在所有场景下都能正常显示!🖥️
