React默认是一个客户端渲染框架,浏览器拿到的HTML只是一个空壳,里面挂了一个根节点,真正的页面内容要等JavaScript下载并执行完成之后才会出现。对用户来说这只是一个短暂的等待,但对搜索引擎爬虫来说,问题就严重了:如果爬虫不执行JS或者执行得不完整,抓到的就是一堆空标签,页面关键词、标题、正文统统缺失,收录效果自然好不了。Next.js正是为解决这个问题而生,它提供的服务端渲染能力让页面在服务器上就生成完整的HTML,爬虫一抓一个准。这篇文章从原理到实战,带你完整了解Next.js的SSR开发。

为什么React单页应用会有SEO痛点
要理解Next.js的价值,先得弄清楚客户端渲染(CSR)的工作机制。传统的React项目,服务器返回的HTML大致长这样:
<html>
<head>
<title>我的应用</title>
</head>
<body>
<div id="root"></div>
<script src="/bundle.js"></script>
</body>
</html>注意那个空的<div id="root">,这就是问题所在。所有页面内容都要等bundle.js下载执行完毕后,由ReactDOM.render填充进去。虽然Google官方宣称它的爬虫能够执行JavaScript,但实际测试中这个执行过程存在延迟,而且不稳定,百度等国内搜索引擎对JS渲染的支持就更弱了。对一个内容型网站、电商详情页或者资讯站来说,这意味着大量页面无法被正常收录,流量损失非常直接。
除了SEO,CSR还有首屏白屏时间长的体验问题。用户在网络较差的环境下,可能盯着空白页面好几秒才能看到内容。服务端渲染一次性解决了这两个问题:服务器直接返回带内容的HTML,爬虫能读、用户能看,后续的JS加载完成后再进行水合(hydration),把静态HTML变成可交互的React应用。
Next.js的几种渲染模式:SSR只是其中一种
很多初学者以为Next.js等于SSR,这其实是个误解。Next.js支持三种预渲染策略,理解它们的区别是选型的基础。
第一种是静态生成(SSG),页面在构建时就把HTML生成好,部署后每个请求直接返回现成的文件,速度最快,CDN友好,适合博客、文档、营销页这类内容不常变化的场景。第二种是服务端渲染(SSR),每次用户请求到达时服务器现场生成HTML,内容永远最新,适合搜索结果页、用户个人中心这类实时性要求高的页面。第三种是增量静态再生(ISR),介于两者之间,静态页面可以按设定的间隔自动重新生成,兼顾性能和内容更新。
判断用哪种模式的核心问题是:这个页面的内容依赖不依赖请求时的数据?如果一篇博客文章构建后就不变了,用SSG;如果页面内容每个用户、每次请求都可能不同(比如登录状态、查询参数),就用SSR。下面重点看SSR的实现方式。
Pages Router中的getServerSideProps实战
在Pages Router目录结构下,pages文件夹里的每个文件自动成为路由。要实现服务端渲染,核心是使用getServerSideProps函数,它只会在服务端执行,永远不会打包进客户端代码。
import React from 'react';
// 页面组件,通过props接收服务端取到的数据
export default function Article({ article }) {
return (
<div>
<h1>{article.title}</h1>
<p>{article.summary}</p>
</div>
);
}
// 这个函数只在服务端执行,每次请求都会运行
export async function getServerSideProps(context) {
const { params } = context; // 可以拿到动态路由参数
const res = await fetch(`https://api.ipipp.com/articles/${params.id}`);
const article = await res.json();
return {
props: { article }, // 返回的数据会作为props传给页面组件
};
}这段代码的执行流程是:用户请求到达Next.js服务器,服务器先执行getServerSideProps拿到数据,接着用这些数据渲染页面组件生成完整HTML,最后把HTML连同序列化后的数据一起返回给浏览器。浏览器收到后先展示HTML内容,React JS加载完成后再接管页面完成水合,整个过程用户几乎感知不到白屏。
几个实用的细节值得注意。context对象里除了params,还有req、res、query等字段,可以读取Cookie、请求头做权限判断。如果检测到用户未登录,可以直接在getServerSideProps里返回redirect做服务端跳转,避免页面闪烁。另外,props返回的数据必须是可JSON序列化的,不能包含函数、Date对象等,传Date会被序列化成字符串,这是新手常踩的坑。
App Router时代的服务端组件方案
Next.js 13之后推出了App Router,渲染逻辑有了新的形态。默认情况下,App Router中的所有组件都是React服务端组件(RSC),它们只在服务端执行,不会打包到客户端,获取数据可以直接用async组件,不再需要getServerSideProps。
// app/articles/[id]/page.jsx
import React from 'react';
// 服务端组件直接async,fetch发生在服务器上
export default async function ArticlePage({ params }) {
const res = await fetch(`https://api.ipipp.com/articles/${params.id}`, {
cache: 'no-store', // 禁用缓存,等价于SSR实时渲染
});
const article = await res.json();
return (
<article>
<h1>{article.title}</h1>
<p>{article.summary}</p>
</article>
);
}这里的关键是fetch的缓存行为控制:不加选项时Next.js默认缓存请求结果,行为接近SSG;加上cache: 'no-store'就是标准的SSR,每次请求都重新获取;用next: { revalidate: 60 }则实现ISR,每60秒重新验证一次。数据获取策略就这样统一到了fetch的配置上,代码更简洁直观。
需要注意服务端组件的限制:不能使用useState、useEffect这类Hook,不能绑定浏览器事件。需要交互的部分要用'use client'指令声明为客户端组件,服务端负责内容和SEO,客户端负责交互,两者通过props传递数据,这种分工让包体积和SEO效果都得到优化。
SEO配套配置:让搜索引擎读懂你的页面
SSR解决了内容可抓取的问题,但完整的SEO还需要配置好head标签。Pages Router推荐使用next/head组件,App Router则提供了声明式的metadata导出。
// App Router中导出静态metadata
export const metadata = {
title: 'Next.js SSR入门指南',
description: '详解Next.js服务端渲染原理与实践',
openGraph: {
title: 'Next.js SSR入门指南',
type: 'article',
},
};
// 需要动态标题时,使用generateMetadata函数
export async function generateMetadata({ params }) {
const article = await getArticle(params.id);
return { title: article.title, description: article.summary };
}除了title和description,别忘了配置canonical标签避免重复内容问题,给图片加上alt属性,以及生成sitemap.xml和robots.txt。Next.js提供了app/sitemap.js和app/robots.js这两个约定文件,动态生成站点地图非常方便,对内容型站点来说这是提升收录效率的重要一环。
最后提一点性能建议:SSR虽然对SEO友好,但每次请求都在服务器执行渲染,会消耗服务端资源,流量上来后要注意缓存策略,能用SSG或ISR的场景就不要硬上SSR。结合CDN缓存、页面级缓存和服务端组件流式渲染,Next.js完全可以做到既对爬虫友好,又给用户秒开体验。