#MPA 和 SPA 对比

理解多页应用程序(MPA)和单页应用程序(SPA)的区别和取舍是理解 Islands 架构的关键,这也可以让你更好的使用 Rpress。

#什么是 MPA 和 SPA ?

MPA 和 SPA 是构建前端页面常见的两种方式。

MPA (Multi-page application) 即多页应用,是从服务器加载多个 HTML 页面的应用程序。每个页面都彼此独立,有自己的 URL。当单击 a 标签链接导航到另一个页面时,浏览器将向服务器发送请求并加载新页面。例如,传统的模板技术如 JSP、Python、PHP 等都是基于 MPA 的框架。

SPA (Single-page application) 即单页应用,它只有一个不包含具体页面内容的 HTML,当浏览器拿到这份 HTML 之后,会请求页面所需的 JavaScript 代码,通过执行 JavaScript 代码完成 DOM 树的构建和 DOM 的事件绑定,从而让页面可以交互。如现在使用的大多数 Vue、React 中后台应用都是 SPA 应用。

#对比

#1. 性能

在 MPA 中,服务器将响应完整的 HTML 页面给浏览器,但是 SPA 需要先请求客户端的 JS Bundle 然后执行 JS 以渲染页面。因此,MPA 中的页面的首屏加载性能比 SPA 更好。

但 SPA 在后续页面加载方面有更好的性能和体验。因为 SPA 在完成首屏加载之后,在访问其它的页面时只需要动态加载页面的一部分组件,而不是整个页面。而且,当页面发生跳转时,SPA 不会重新加载页面,对用户更友好。

#2. SEO

MPA 中服务端会针对每个页面返回完整的 HTML 内容,对 SEO 更加友好;而 SPA 的页面内容则需要执行 JS 才能拉取到,不利于 SEO。

#3. 路由

MPA 在浏览器侧其实不需要路由,每个页面都在服务端都有一份 URL 地址,浏览器拿到 URL 直接请求服务端即可。

但 SPA 则不同,它需要 JS 掌管后续所有路由跳转的逻辑,因此会引入一些路由方案来管理前端的路由,比如基于 hashchange 事件或者浏览器 history API 来实现。

#4. 状态管理

除了路由,SPA 另外一个复杂的点在于状态管理。SPA 当中所有路由的状态都是由 JS 进行管理,在不同的路由进行跳转时通过 JS 代码进行一些状态的流转,在页面的规模越来越大的时候,状态管理就变得越来越复杂了。因此,社区也诞生了不少的状态管理方案,如传统的 Redux、社区新秀 Valtio、Zustand 包括字节自研的 Reduck,都是为了解决 SPA 状态管理的问题,一方面降低操作的复杂度、另一方面引入一些规范和限制(比如 Redux 中的 action 机制)来提高项目可维护性。

#取舍

总而言之,MPA 有更好的首屏性能,SPA 在后续页面的访问中有更好的性能和体验,但 SPA 也带来了更高的工程复杂度、略差的首屏性能和 SEO。这样就需要在不同的应用场景中做一些取舍。

不过,MPA 和 SPA 也并不是完全割裂的,两者也是能够有所结合的,比如 SSR/SSG 同构方案就是一个典型的体现,首先框架侧会在服务端生成完整的 HTML 内容,并且同时注入客户端所需要的 SPA 脚本。这样浏览器会拿到完整的 HTML 内容,然后执行客户端的脚本事件的绑定(这个过程也叫 hydrate),后续路由的跳转由 JS 来掌管。当下很多的框架都是采用这样的方案,比如 Next.js、Gatsby。

#Islands 架构引入

但实际上,把 MPA 和 SPA 结合的方案也并不是完美无缺的,主要的问题在于这类方案仍然会下载全量的客户端 JS 及执行全量的组件 Hydrate 过程,造成页面的首屏 TTI 劣化。

我们可以试想对于一个文档类型的站点,其实里面的大多数组件是不需要交互的,主要以静态页面的渲染为主,因此直接采用 MPA 方案是一个比 MPA + SPA 更好的一个选择。进一步讲,对于更多的轻交互、重内容的应用场景,MPA 也依然是一个更好的方案。

由于页面中有时仍然不可避免的需要一些交互的逻辑,那放在 MPA 中如何来完成呢?这就是 Islands 架构所要解决的问题。你可以下一小节孤岛架构了解更多细节。

#Islands 实现的框架

对于Astro,孤岛架构适用的对象是组件。而在Qwik中,孤岛架构最细的粒度是组件中的某个方法