我已经做了大约4年的网页编程,最近我终于开始在一个服务器端渲染的框架上重建一个完整的项目。从纯HTML和CSS做起,一路走来,到现在使用现代库如React,虽然我在合同项目中接触过服务器端渲染(SSR),但我从未从头开始构建过一个这样的服务器端渲染项目。
SSR的基本介绍如果你还不了解SSR是什么,或者不知道为什么它很重要(就像我之前不了解一样),SSR是一种在服务器端渲染你的应用,并将渲染好的HTML发送给客户端的方法。这意味着用户的浏览器里不会运行任何JavaScript。如果某个组件需要运行自己的JS(即需要被水化),你可以把它切换到客户端进行渲染。
当我第一次听说SSR时,我认为它仅仅是在使用高资源消耗框架时,减少旧设备负担的一种方法。在某种程度上,这在某种程度上仍然是SSR的应用场景之一,但最终我明白了它真正的、非常简单的理由:——爬虫抓取的元数据和SEO优化。
使用Next构建的网站在Ahrefs的得分
我想为我的工具包网站(snippp.io)建立索引并生成丰富的预览效果,以便工具包和代码片的标题在Twitter和Bluesky上就会显示,并且能够被搜索引擎抓取。
这个原版的 app 是用 React 开发的,作为一名用户,这个 app 运行得非常完美:你加载页面,它就会立刻显示内容!浏览器的标题会显示你正在查看的内容的名称,感觉一切都很完美。
然而,在点击链接和页面显示之间,有在几毫秒内发生的几个步骤被忽略了。React 总是在第一次渲染时呈现一个正在加载中的页面版本。通常来说,React 只会在渲染完成后才触发任何 fetch 请求。
经典的客户端处理方式重要的事情发生在这几毫秒里。我们来看看标准React应用的操作顺序,这可能比你想象的更重要。
- 当有人点击该 URL 时,React 会渲染一个空版本的页面作为占位符。这大概是一个空的加载页面,因为 React 需要加载一些内容以供获取。
- React 会触发你组件中的任何
useEffect
钩子。在我们的例子中,它会发送一个请求从数据库获取一些信息并等待响应。 - 大约 200 毫秒后,数据库返回了我们需要填充页面的数据。React 继续之前的渲染过程,设置页面的内容,填充浏览器的标题,页面看起来正是我们想要的样子。
所以,从用户的角度看,一切都很棒。他们点击链接,250毫秒后就能看到你的内容。但对爬虫来说,要么无法发起请求,要么无法等待250毫秒,你的页面可能会一直显示为一个正在加载的页面,且页面标题为空。
开始服务端渲染!在 Next.js 中,我们能够创建一个服务器端运行的组件,该组件会先获取我的信息数据,用这些信息填充页面的元数据信息,然后用这些信息渲染页面的其余部分。因此,当一个爬虫向我的服务器请求页面时,它会收到一个最终的 HTML 页面进行爬取,而不是一个稍后填充内容的页面骨架。
为了完整性,将这250毫秒(或可能更少)拆分一下!
- 当有人点击这个链接时,服务器首先发送一个请求来获取填充页面所需的数据,然后等待。
- 数据返回后,元数据和页面标题被设置,页面主体则使用这些数据进行填充并转换为HTML。
- 服务器将相应的HTML发送回请求它的客户端,页面仅渲染一次,所有信息都已经准备好了。
现在,爬虫、元数据请求者和搜索引擎索引器可以从一开始就能看到你所有的内容!为分享链接生成丰富的预览,让搜索引擎索引你所有的内容,并获得你所选SEO工具中的100%健康得分。
SSR是不是更快?
我提到我们可以通过使用服务端渲染来缩短请求时间,但它是如何工作的呢?其实,一个客户端应用总是从客户端位置访问数据库。所以如果数据库在法国,而你的服务器从美国加州访问它,这显然会比从西班牙访问要慢一些。
不过,如果你能将你的数据库和服务器都放在同一个区域或同一个地理位置,你可以大幅降低数据库的响应延迟。尽量把它们部署在同一个本地网络里,甚至在同一栋楼或同一个数据中心里。
当然,两者之间仍有差距,但在仅发送完全渲染的HTML时,传输速度仍然可能非常快。如果服务器运行了适当的硬件,页面渲染时间甚至可能比客户端设备更快。
考虑一些其他的优化,比如访问缓存数据,可以让服务器端渲染流程比普通的 React 应用更快,并且学习曲线非常平缓。例如:
学 Next 难不难?来自 React 出发,理解起来非常容易,只是因为我在新的基础上重建我的应用,感觉有些繁琐。我把所有的组件、工具及原仓库里的其他一切内容都搬过来了,这样我有了一个良好的开端,不过我认为一开始就为服务器端渲染构建所有内容可能更简单。这里有一些关于我的经历值得注意的地方:
- 将我的文件结构转换为 Next.js 的 app 路由 — 简单
- 将我的 api 文件结构和处理器转换为 Next.js 的 api 路由 — 简单但非常耗时。在一个有30多个处理器的应用中,这需要花时间重新格式化并测试每个接口
- 将组件拆分成更小的模块,以便父组件可以服务器渲染,而其他部分可以依赖于仅客户端的 api,如 localStorage,这样我就需要不断重写相同组件3或4次以学习新技巧 — 中等难度
- 生成元数据 — 简单,
- AdSense 集成 — 简单,网上有很多关于在 Next 中集成广告位的信息
- 键盘快捷键 — 中等难度,我遇到很多问题使现有的键盘事件监听器正常工作,最终重写了所有监听器的创建。如果从头开始,可能会简单一些,我认为只要正确实现水合,Next.js 也能支持非常交互式的应用
最近我用自己制作的一个模板创建了许多原型 _(Vite、React、TS、Tailwind、Prettier、Lucide、React Router 和 Vercel 分析工具,可在此获取 ) ,但我可以将 React Router 换成 Next.js 并调整文件夹结构。这样我认为可以更好地着手原型开发,更快速且简便,并且如果我想要在此基础上构建完整的应用,还能改善 SEO。
在未来,我很想尝试其他SSR框架如Next,看看它们的工具能为其他使用场景提供多少帮助,但我现在理解为什么大家都倾向于使用Next了。从React转过来学Next很容易,大多数你想要在服务器上做的事情都被很好地抽象化了,而且只需简单地切换即可使组件保持活性。由于Next的高度抽象性,有些细节上的微调工作可能无法实现,不过我还没遇到需要进行这种细节调整的情况。