浅谈前端群岛架构模式
从单页面应用到服务端渲染
如果你学习过 vue 或 react 这类专注处理 DOM 变化的框架,你可能知道在脚手架通过他们制作出来的页面是一个空白的 HTML,然后页面通过引入 js 去渲染通过那些 UI 框架制作出来的页面
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" name="format-detection" content="telephone=no" />
<meta name="viewport" content="width=device-width,user-scalable=no" />
<meta name="description" content="React + Typescript + SSR + Code-splitting" />
<meta name="google" content="notranslate" />
<link rel="icon" href="/providen-craft-website/assets/favicon.0eec0e1c.ico" type="image/x-icon" />
<title>Hello World!</title>
<!-- 引入 js 来渲染页面 -->
<script type="module" crossorigin src="/providen-craft-website/assets/index.bbbde41e.js"></script>
<link rel="modulepreload" crossorigin href="/providen-craft-website/assets/vendor.e37f750a.js">
<link rel="stylesheet" href="/providen-craft-website/assets/index.878b5f47.css">
</head>
<body>
<div id="root"></div>
</body>
</html>
那么这种页面的优缺点是什么?
优点:
- 页面加载后如果用户在页面内通过路由切换到其他页面加载速度比较快,体验较好
- 首次渲染后只需要向服务器请求 json 数据,服务器压力较小
缺点:
- 首屏加载时间比较长,因为项目变大了话 js 的文件就会比较大
- 对 SEO 不友好,如果搜索引擎没有将抓取到的页面内的 js 进行加载的能力,那么将会对搜索排名很不利
为了解决这种 UI 框架带来的弊端,人们后面推出了一种新的渲染方法:SSR(Server Side Rendering)即服务端渲染
React 对应的 SSR 框架是 Next.js,而 Vue 的 SSR 框架是 Nuxt.js,当然这些框架也有内置的 SSR api 可以使用。
现在我们来看一下目前传统 SSR 的渲染流程:
- 首先在客户端首次请求时,服务端会向客户端发送完整的 HTML 结构
- 发送完成后,需要引入 UI 框架的 js,为预渲染好的 HTML 结构绑定事件,这一步通称为水合(hydrate)
- 当水合过程完成后,页面才具有可交互的性质
如果你想要实际体验,可以查看 Vue 官方使用他们的 SSR api 制作的一个最小用例:https://cn.vuejs.org/guide/scaling-up/ssr.html#rendering-an-app
这也就意味着如果应用体积越大,处理水合过程的 js 文件也会越大,此时增加就不是首屏加载时间,而是前端页面的另一项指标:TTI (Time to Interactive / 可交互时间)会越来越高,同样会导致用户体验越来越差。
为了解决这个问题,一位前端架构师 Katie Sylor-Miller 在 2019 年首次提出了 “群岛架构” 这一概念,其目的皆在优化 SSR 环境下的 TTI 指标,优化用户体验。
什么是 “群岛架构”
在这种架构下,我们首先要做的是对页面进行组件拆分,并对其进行以下分类:
- 静态组件
- 可交互组件
在此我用自己的博客做一个简单示例:
不难看出,可交互组件有两个:Header,索引;静态组件有两个:Footer,正文部分。
如果在传统的 SSR 中,如果将整个页面视作一个 APP,那么所有部分都将通过 SSR 渲染出来,并通过一个 js 对其进行交互绑定,而这些绑定对于静态部分的 HTML 是完全没有必要的。
而在群岛架构模式下,我们不会为只有静态 HTML 部分添加任何额外 js,而是只对有需要的部分进行处理,以此减少需要加载的 js 文件体积。
而可交互组件也可以进一步细分出优先级,让最高优先级的部分优先加载 js,次要的部分则慢慢处理。
在 SSR + 群岛架构的环境下,用户首先能通过搜索引擎找到排名靠前的网站;其次通过 SSR 能看到首屏渲染的内容;最后在水合的过程中,重要的组件会先被处理,在页面完全被水合处理之前,页面已经变得具有可交互性质,TTI 时间也因此降低了。
到这里,你可能想问:花费如此大的精力去提升各种网页指标与体验究竟是为了什么?
答案很简单:效益与金钱
对于以内容为中心的网站,如电商、文档等,高加载速度与内容呈现和交互速度都是非常重要的
- 每快 100ms → 转化率增加 1% (Mobify, 收入 +$380,000/年)
- 每快 50% → 销售额增加 12% (AutoAnything)
- 每快 20% → 转换率增加 10% (Furniture Village)
- 每快 40% → 注册率增加 15% (Pinterest)
- 转换速度提高 850ms → 转化率增加 7% (COOK)
- 每慢1秒 → 减少 10% 的用户 (BBC)
目前基于群岛架构实现的框架
目前主流的全栈且实现了群岛架构的框架有两款:Astro
与 Qwik
笔者在这里只对 Astro 做简短的介绍与展示使用示例,对更深入的内容或对另外框架有兴趣的读者可以前往他们对应的官网阅读文档以及体验
Astro官网:https://astro.build
qwik官网:https://qwik.builder.io/
Astro 是什么?
Astro 是一款全栈 SSR 的 Web 框架,他的
创建项目与启动项目
npm create astro@latest
npm run dev
Astro 官方在 VSCode 有发布语法高亮插件,可以直接搜索下载
编写一个 Astro 组件
每个 Astro 组件都以 .astro
扩展名结尾,通过这种文件生成的 html 不携带任何 js,保证了页面是纯静态的,除非手动添加客户端 js
在组件中可以通过 import 的方式引入其他组件
Astro 本身使用的是 jsx 语法来表明 html 标签与组件,这意味着拥有 react 基础的开发者可以毫无压力的上手框架
引入我们熟悉的 UI 框架
在这里举例引入 vue 框架
首先进行安装:
npm install @astrojs/vue
npm install vue
然后在 astro 的配置文件中引入插件:
// astro.config.*
import { defineConfig } from 'astro/config';
// 引入插件
import vue from '@astrojs/vue';
export default defineConfig({
// ...
// 添加 vue 集成
integrations: [vue()],
});
随后就可以在 astro 组件中使用了!
在控制台也可以看到被 vue 开发者工具识别了
如果检查控制台的元素会发现具有可交互性的组件被包围到了 astro-island 中,而未被激活的部分只渲染出了静态 html
此时就是一个群岛架构的最简单示例:静态不具有交互性的 HTML 直接被插入到页面中,而具有交互性的组件则被围了起来,如同 HTML 海洋中的一个岛屿
群岛架构的局限性
通过以上的内容看起来似乎这种架构有许多好处,但他并不是百分百万能的框架,就 Astro 官方文档也写明了这个框架适合用于构建以内容为中心的网站:大多数营销网站、出版网站、文档网站、博客、个人作品集和一些电子商务网站。
而如果用这个框架去构建应用功能复杂的网站比如:仪表盘、邮箱、社交应用,甚至是像 即时设计 和 Figma 这种体验接近于桌面端应用的 Web 应用程序,那么 Astro 不会发挥出特别大的作用。因为应用本身具有非常强的交互性质,包含大量的 DOM 变化需要处理,这也是为什么 Vue 和 React 直接构建出来的网页被称为 SPA (单页面应用)的原因。这类应用也更适合使用他们自己的 SSR 框架。
实际开发中我们更需要追求一个工具是否适合我们的需求,能为我们解决什么问题,而不是因为他有什么优点,或者是用了这个就比其他网站更高端等原因。在这里引用 Redux 的作者 Dan Abramov 的话说就是:
Flux 架构就像眼镜:您自会知道什么时候需要它。
文章参考资料
https://zhuanlan.zhihu.com/p/552852057 - 从 Islands Architecture 看前端有多卷
https://docs.astro.build/zh-cn/concepts/why-astro/ - 为什么是 Astro?
https://docs.astro.build/zh-cn/concepts/islands/ - Astro 群岛