单页应用的服务器端渲染:解决搜索引擎索引难题
Googlebot、YandexBot 等搜索引擎爬虫在抓取基于客户端渲染(CSR)的单页应用(SPA)时,常仅看到空 HTML。由于不等待 JavaScript 执行,它们会跳过动态内容,导致页面无法被索引。服务器端渲染(SSR)通过在服务端预先生成完整 HTML 再发送至浏览器,彻底解决该问题。此举显著提升首屏内容绘制(FCP)、最大内容绘制(LCP)和首字节时间(TTFB),从而优化核心网页指标(Core Web Vitals)并增强自然搜索可见性。
渲染方案对比
| 参数 | 客户端渲染(CSR) | 服务器端渲染(SSR) | 预渲染(Prerendering) | 静态站点生成(SSG) |
|----------|-----|-----|--------------|-----|
| 渲染位置 | 浏览器端 | 服务端(按请求) | 构建时针对特定 URL 预生成 | 构建时为全部页面预生成 |
| FCP/LCP 速度 | 较慢 | 快 | 快 | 极快 |
| SEO 效果 | 差 | 优秀 | 良好 | 优秀 |
| 数据实时性 | 实时更新 | 实时更新 | 数据陈旧 | 数据陈旧 |
| 服务端负载 | 极低 | 高 | 低 | 极低 |
SSR 最适合动态内容页面——如商品目录、新闻流、数据看板;预渲染适用于更新频率低的页面(例如营销落地页);而 SSG 则是文档、官网首页等静态内容的理想选择。
React + Next.js:手把手实现 SSR
创建新项目:
npx create-next-app@latest my-ssr-app
cd my-ssr-app
npm run dev
对于动态页面,使用 getServerSideProps:
// pages/product/[id].jsx
export async function getServerSideProps(context) {
const { id } = context.params;
const res = await fetch(`https://api.example.com/products/${id}`);
const product = await res.json();
if (!res.ok) {
return { notFound: true };
}
return {
props: { product },
};
}
export default function ProductPage({ product }) {
return (
<main>
<h1>{product.name}</h1>
<p>{product.description}</p>
</main>
);
}
添加 SEO 友好的元标签:
import Head from 'next/head';
export default function ProductPage({ product }) {
return (
<>
<Head>
<title>{product.name} | 在线商城</title>
<meta name="description" content={product.shortDescription} />
<meta property="og:title" content={product.name} />
<meta property="og:image" content={product.imageUrl} />
</Head>
<main>
<h1>{product.name}</h1>
</main>
</>
);
}
部署建议:Vercel、Netlify 或搭配 PM2 的 Node.js 服务。
无错误水合(Hydration)
水合是指客户端 React “激活”服务端已渲染 HTML 的过程。避免水合失败的关键点:
- 始终保持服务端与客户端数据同步;
- 在调用
window或document前,务必检测运行环境; - 密切关注浏览器控制台警告信息。
const isBrowser = typeof window !== 'undefined';
Vue + Nuxt.js:服务端渲染实践
初始化项目:
npx nuxi@latest init my-nuxt-app
cd my-nuxt-app
npm install
npm run dev
服务端获取数据:
<!-- pages/product/[id].vue -->
<script setup>
const route = useRoute();
const { data: product, error } = await useFetch(
`/api/products/${route.params.id}`
);
if (error.value) {
throw createError({ statusCode: 404, message: '页面未找到' });
}
useHead({
title: product.value?.name,
meta: [
{ name: 'description', content: product.value?.shortDescription },
],
});
</script>
<template>
<main>
<h1>{{ product?.name }}</h1>
<p>{{ product?.description }}</p>
</main>
</template>
配置 nuxt.config.ts:
export default defineNuxtConfig({
ssr: true,
app: {
head: {
htmlAttrs: { lang: 'zh-CN' },
link: [{ rel: 'canonical', href: 'https://example.com' }],
},
},
nitro: {
preset: 'node-server',
},
});
Angular Universal:SSR 集成方案
Angular 17+ 版本:
ng add @angular/ssr
在 server.ts 中定义服务端入口:
import { APP_BASE_HREF } from '@angular/common';
import { CommonEngine } from '@angular/ssr';
import express from 'express';
// ... 其他导入
const server = express();
// ...
server.get('*', (req, res, next) => {
commonEngine
.render({
bootstrap: AppServerModule,
documentFilePath: join(browserDistFolder, 'index.html'),
url: req.originalUrl,
publicPath: browserDistFolder,
providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }],
})
.then((html) => res.send(html))
.catch(next);
});
构建并启动服务:
npm run build:ssr
npm run serve:ssr
安全检测运行平台:
import { isPlatformBrowser } from '@angular/common';
import { inject, PLATFORM_ID } from '@angular/core';
const platformId = inject(PLATFORM_ID);
if (isPlatformBrowser(platformId)) {
console.log(window.location.href);
}
与无头 CMS 集成
Next.js + Contentful GraphQL 示例:
export async function getServerSideProps({ params }) {
const query = `
query GetArticle($slug: String!) {
articleCollection(where: { slug: $slug }) {
items {
title
body
seo {
title
description
}
}
}
}
`;
const res = await fetch(
`https://graphql.contentful.com/content/v1/spaces/${process.env.CONTENTFUL_SPACE_ID}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.CONTENTFUL_TOKEN}`,
},
body: JSON.stringify({ query, variables: { slug: params.slug } }),
}
);
const { data } = await res.json();
// ...
}
核心要点总结
- SSR 向搜索引擎爬虫交付完整 HTML,从根本上解决 CSR 应用的索引失效问题;
- Next.js、Nuxt.js 和 Angular Universal 是经过大规模生产验证的 SSR 框架;
- 水合不一致将触发整页重绘——务必严格校验运行时环境;
- 切换 SSR 后,核心网页指标(FCP、LCP、TTFB)平均提升 50–80%;
- 建议集成 Redis 缓存,在流量高峰期间将 TTFB 稳定控制在 200 毫秒以内。
— Editorial Team
暂无评论。