关于 server-side-rendering
面向 Web 开发者的服务端渲染指南,介绍服务器生成 HTML、常见收益与限制,以及 React 和 Next.js 的实现方式。适用于重视 SEO 和快速呈现内容的页面。
主要功能
- 01服务端生成页面 HTML
- 02减少客户端渲染所需 JavaScript
- 03支持 React 水合
- 04介绍 Next.js SSR 实现
适用场景
- 01为搜索引擎优化内容页面
- 02让内容密集页面更快显示
- 03在 React 页面中结合服务端渲染与客户端交互
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ patternsdev/ skills --skill server-side-rendering安装后在 Claude Code 中输入/server-side-rendering确认已加载。
详细介绍
服务端渲染
目录
服务端渲染(SSR)是最古老的网页内容渲染方式之一。SSR 会在响应用户请求时,生成页面内容的完整 HTML。内容可能包含来自数据存储或外部 API 的数据。
连接和获取数据的操作都在服务器上处理。用于格式化内容的 HTML 也由服务器生成。因此,使用 SSR 可以避免额外的数据获取和模板处理往返请求。这样一来,客户端不需要渲染代码,也无需向客户端发送对应的 JavaScript。
适用场景
- 当 SEO 和快速的首次内容绘制(FCP)对应用很重要时,使用此方案
- 适用于内容繁多、需要快速呈现给用户和搜索引擎的页面
不适用场景
- 对于纯静态内容,静态渲染(SSG)已足够,并且可以避免按请求产生的服务器成本
- 对于 SEO 无关紧要的内部仪表板或工具,CSR 架构更简单
- 当每次请求的服务端渲染开销过高且无法进行缓存时
说明
- 使用 Next.js 等提供内置 SSR 支持的框架
- 考虑升级到
renderToPipeableStream(React 18+),以支持结合 Suspense 的流式 SSR - 将 SSR 与客户端水合结合,用于交互式页面
- 注意 TTFB 的影响——优化服务器响应时间并考虑缓存
- 探索将 React Server Components 作为 SSR 的补充,以减少客户端 JavaScript
详情
在 SSR 中,每个请求都会被单独处理,服务器会将其作为新请求进行处理。即使连续两个请求的输出没有太大区别,服务器仍会从头开始处理并生成内容。由于服务器由多个用户共用,因此在任意时刻,处理能力都由所有活跃用户共享。
经典 SSR 实现
下面通过一个简单示例,展示如何使用经典 SSR 和 JavaScript 在页面上显示并更新当前时间。
<!DOCTYPE html>
<html>
<head>
<title>Time</title>
</head>
<body>
<div>
<h1>Hello, world!</h1>
<b>It is <div id=currentTime></div></b>
</div>
</body>
</html>
function tick() {
var d = new Date();
var n = d.toLocaleTimeString();
document.getElementById("currentTime").innerHTML = n;
}
setInterval(tick, 1000);
请注意,这与生成相同输出的 CSR 代码有所不同。还要注意,虽然 HTML 是由服务器渲染的,但此处显示的时间是客户端本地时间,由 JavaScript 函数 tick() 填充。如果要显示其他与服务器相关的数据(例如服务器时间),则需要在 HTML 渲染之前将其嵌入其中。这意味着若不与服务器进行往返请求,这些数据就不会自动刷新。
优缺点
在服务器上执行渲染代码并减少 JavaScript,可带来以下优势。
JavaScript 更少,FCP 和 TTI 更快
当页面中有多个 UI 元素和应用逻辑时,与 CSR 相比,SSR 所需的 JavaScript 会少得多。因此,加载和处理脚本所需的时间也更短。FP、FCP 和 TTI 都会更短,且 FCP = TTI。使用 SSR 时,用户不必等待所有屏幕元素出现并变为可交互状态。
为客户端 JavaScript 腾出更多预算
开发团队需要遵守 JS 预算,以限制页面上的 JS 数量并达到预期性能。使用 SSR 时,由于页面渲染所需的 JS 被直接移除,因此可以为应用可能需要的第三方 JS 腾出更多空间。
支持 SEO
搜索引擎爬虫可以轻松抓取 SSR 应用的内容,从而提升页面的搜索引擎优化效果。
得益于上述优势,SSR 非常适合静态内容。不过,它也有一些缺点,因此并非适用于所有场景。
TTFB 较慢
由于所有处理都在服务器上进行,在以下一种或多种情况下,服务器的响应可能会延迟:
- 多个用户同时访问,导致服务器负载过高。
- 网络较慢。
- 服务器代码未经过优化。
某些交互需要重新加载整个页面
由于客户端上没有全部代码,所有关键操作都需要频繁往返服务器,从而导致重新加载整个页面。这可能会延长交互间隔,因为用户需要等待更长时间才能进行下一步操作。因此,使用 SSR 无法实现单页应用。
使用 Next.js 实现 SSR
Next.js 框架也支持 SSR。它会在每次请求时于服务器上预渲染页面。可通过从页面导出一个名为 getServerSideProps() 的异步函数来实现,如下所示。
export async function getServerSideProps(context) {
return {
props: {}, // will be passed to the page component as props
};
}
context 对象包含 HTTP 请求和响应对象、路由参数、查询字符串、区域设置等键。
以下实现展示了如何使用 getServerSideProps(),以 React 格式在页面上渲染数据:
// data fetched from an external data source using `getServerSideProps`
const Users = ({ users, error }) => {
return (
<section>
<header>
<h1>List of users</h1>
</header>
{error && <div>There was an error.</div>}
{!error && users && (
<table>
<thead>
<tr>
<th>Username</th>
<th>Email</th>
<th>Name</th>
</tr>
</thead>
<tbody>
{users.map((user, key) => (
<tr key={key}>
<td>{user.username}</td>
<td>{user.email}</td>
<td>{user.name}</td>
</tr>
))}
</tbody>
</table>
)}
</section>
);
};
export async function getServerSideProps() {
try {
// Fetch data from external API
const res = await fetch("https://jsonplaceholder.typicode.com/users");
const data = await res.json();
// Pass data to the page via props
return { props: { users: data, error: null } };
} catch (error) {
return { props: { users: null, error: true } };
}
}
export default Users;
使用 React 实现服务器端渲染
React 可以进行同构渲染,这意味着它既能在浏览器中运行,也能在服务器等其他平台上运行。因此,可以使用 React 在服务器上渲染 UI 元素。
React 还可以与通用代码搭配使用,让同一份代码在多个环境中运行。这得益于在服务器上使用 Node.js。
ReactDOMServer.renderToString(element);
此函数会返回与 React 元素对应的 HTML 字符串。随后可将 HTML 渲染到客户端,以加快页面加载速度。
renderToString() 函数可以与 hydrateRoot() 搭配使用。这样既能保留在服务器上渲染的 HTML,也能在客户端上附加事件处理程序。
为实现这一点,我们会在客户端和服务器上为每个页面各使用一个 .js 文件。服务器上的 .js 文件负责渲染 HTML 内容,客户端上的 .js 文件负责对其进行水合。
服务器端代码:
app.get("/", (req, res) => {
const app = ReactDOMServer.renderToString(<App />);
});
用于确保元素 App 完成水合的客户端代码:
import { hydrateRoot } from "react-dom/client";
hydrateRoot(document.getElementById("root"), <App />);
此处提供了一个完整的 React SSR 示例。