react-static 静态网站搭建(一):什么是 React 静态网站?

2019-03-12

前言

maxiee.github.io 是一个基于 react-static 的 React 静态网站。它与传统的静态博客有很多异同。在这个系列中,我准备介绍 react-static,它是一个非常优秀的 React 静态网站生成器。本文是第一篇,我们先来看看 React 静态网站。

React 静态网站与传统的静态博客相比,有很多相同之处,也有很多异同之处。在本文中,我试图将这些异同讲清楚,希望能展示出 React 静态网站对静态网站开发带来的巨大提升。

首先,我们先从最简单的 HTML 网页说起。

静态 HTML 网页

让我们回到互联网的原始时代,没有数据库、没有 PHP、没有 API。

那时的主页真的就只是一个 HTML 文件,比如上面写着:

“欢迎来到 Maxiee 的主页!我是一个 Linux 爱好者,如果你想与我交朋友,请给我发邮件或者打电话,我的联系方式如下:……”

这就是最简单的网页,它的结构如下:

其中:

  • CSS 是主页的样式
  • body 部分是我们的网页内容
  • js 在早期往往是一些小特效,比如背景雪花、跟随鼠标的粒子效果这种

这种网页我们将之成为静态网页(没有 API 请求、没有数据库查询,只需要文件服务器托管即可展示)。

动态网页

为了更好地理解静态 HTML 网页,我们再来说说他的反义词,动态网页。

动态网页往往是跟 Web 框架相关联,比如 Django、Flask、Laravel、Wordpress。

对于一个传统的 MVC 网站,当用户访问 url 时,Web 框架会经历这些过程:

  • 收到 HTTP 请求,解析 url 路径
  • 根据路径(文章页面)进行数据库检索,获取数据
  • 加载网页模板(文章页),将数据填充到模板当中,动态生成 HTML
  • 将生成好的 HTML 通过 HTTP 响应返回给用户

从中可以看出,动态网页是在用户请求时现学现卖,匆匆忙忙拼凑出页面返回。

而静态网页则是一开始就把数据填充进去了,什么时候要就直接把文件扔过去,更潇洒一些。

静态博客

静态博客(Hexo、Jekyll、Hugo……)同样也是基于上节中古老的静态网页技术。如果你搭建并使用过静态博客,对其应该再熟悉不过了。

GitHub Pages 就是著名的静态网站托管服务。我的 maxiee.github.io 就是托管在上面。任何人写一个静态的 HTML 页面,都可以部署到 GitHub Pages,作为静态网站供人访问。

使用静态博客进行写作时,我们并不是直接徒手写 HTML(那样效率太低了),而是使用 Markdown 语法进行写作。比如按照如下的目录结构:

.
└── content
    └── about
    |   └── _index.md  // <- https://example.com/about/
    ├── posts
    |   ├── firstpost.md   // <- https://example.com/posts/firstpost/
    |   ├── happy
    |   |   └── ness.md  // <- https://example.com/posts/happy/ness/
    |   └── secondpost.md  // <- https://example.com/posts/secondpost/
    └── quote
        ├── first.md       // <- https://example.com/quote/first/
        └── second.md      // <- https://example.com/quote/second/

来源:HUGO Content Organization

静态博客又称为静态博客生成器,是因为它通过一个命令行,将这些 Markdown 文件经过模板套用,渲染为文章 HTML,又将这些文章网页连同首页、归档页、关于页等等,共同组装成为一个网站,即静态博客。

当生成器构建完成的时候,这个网站的内容变固定下来了,部署到远端后(如 GitHub Pages),用户每次打开都展示同样的内容。

直到下一次需要重新生成(比如写作了新文章),再次执行生成器,经过一番渲染与组装,网站产生了新的快照。

下面我们再用图像来展示这个过程:

① Markdown 文章转换为 HTML 的过程:

② 整个网站,所有 Markdown 转换组装为整个静态博客站点的过程:

React SPA 应用

很多人也许会奇怪,React 怎么会跟静态网站建立起联系来呢?因为说到 React,往往就会提到 SPA(Single Page Application,单页应用)

单页应用是什么呢?在上面的维基百科链接中有详细的定义。在这里我直接给出直观上的认识:

  • 在线的 Photoshop-like 网站,打开网址就是一个图片编辑程序

  • 在线的 Rogue-like 游戏网站,react-rpg

  • 著名的 Gmail

  • Office 365 网页版

提到这些网站我们会发现,他们与传统的新闻网站不同,他们都是应用(Application)软件。

对于传统的新闻网站或者论坛,主要作用是展示内容,同时处理部分表单提交需求。而对于 SPA 来说,交互性大大提高。

React 就是一个现在最流行的 SPA 开发技术。

使用功能 React 开发的 SPA Web 网站有一个特点,它一般由两部分组成:

  • 一个 index.html,里面几乎是空的,除了导入一个 JavaScript 脚本

  • 一堆 JavaScript 脚本,我们把他们整体看待,他们是 Webpack 打包出的 Bundle

  • 这有点像 Android APK。

既然网站的逻辑全在 JavaScript Bundle 中,就会带来一个问题——包体积、启动时间。

针对这个问题的解法是分包、懒加载,这也是前端领域的一个热门话题。

分包、懒加载值得就是把 Bundle 碎片化,在每个页面都只加载其所必要的数据即可。这样既加快了首次打开速度,有不影响用户体验,通过预加载技术,更能将体验达到用户无感知。

SPA 也是静态网站

说到这里不知你发现没有,SPA 也是由纯的 HTML、CSS、Java 所组成的,这与前面的静态 HTML 网页是一样的!

事实也是的确如此。我们用 React 开发的应用,也可以托管在 GitHub Page 上。

注:尽管 GitHub Page 本身没有完全支持,但是通过一点小 hack 即可实现,详情参见

但是也有不同之处,以博客为例,在启动起来之后博客只有一个框架,与 APP 相似,它需要再发起网络请求来拉取数据,比如拉取文章列表。

这个列表既可以有一个 PHP API 返回,也可以是 GitHub Pages 某个路径上的 json 文件。

博客中运行的 React 代码会解析返回的数据,并展示到页面上。

前阵子比较流行的前后端分离,就是指这个事情。这样就把动态网页中网络框架(后端)跟前端页面(前端)解耦了,各玩各的。

SPA 也是静态网站,与静态 HTML 相比,静态 HTML 是所有数据完全包含在内了,而 SPA 只包含了一个壳子,数据需要等壳子加载好后,在通过请求拉取。

SPA 静态化

这一节最让人懵逼了……怎么说呢?看看这一句:在上一节中,我们说 SPA 也是一个静态网站;在本节中,我们来看如何将 SPA 静态化。

你懵了吗?不管你信不信,我反正是懵了。哈哈哈,言归正传。

我们在上一节中说到:

SPA 也是静态网站,与静态 HTML 相比,静态 HTML 是所有数据完全包含在内了,而 SPA 只包含了一个壳子,数据需要等壳子加载好后,在通过请求拉取。

SPA 静态的东西只有一层壳,数据是网页加载好后再去加载的。这里面会有几个问题:

  1. SPA 这层壳的加载也要耗费时间
    • 首先要把 React 框架加载起来(印象里是数十 KB)
    • 之后再把我们基于 React 开发的网站框架(壳)加载起来
      • 这个大小就不好说了,分包懒加载做得好的话小一些,做的不好的话大一些,没经验的话就会很大
      • 最小也要数十 KB
  2. 壳加载好后,需要再发一个请求去拉数据
    • 如果是一篇文章,数据本身可能要几 KB
    • 虽然流量开销不大,但是建立新的 HTTP 请求有时间开销
    • 在网络情况一般的时候可能要几十毫秒

从中可以看出,这种网站是很受网络情况影响的,如果网速不佳,体验会大大下降。

让我们换一个角度来想:

SPA 把壳加载起来,再把数据拉取完成,DOM 在经过一顿折腾后终于固定下来。我们能否用一个照相机,将这个固定下来的 DOM 照下来(Snapshot),这样不就得到了一个普通的静态 HTML 网页吗?

这个网页的大小可能一共就几 KB,直接把他拉下来速度不是更快吗?

这个思路就是 SPA 静态化,换做一个比较流行的说法,叫 SSR(Server-Side Rendering,服务端渲染)。参考文献

两种 SSR

SSR 指的是在服务端就将 React 应用渲染为最终的静态 HTML,返回给用户。

在具体实施时,有两种不同的做法,分别发生在不同的阶段。

网络框架阶段

比较常用的是在网络框架阶段进行渲染。

具体来说,当用户请求 url 的时候,网络框架获取数据,同时在 Server 侧执行 React 应用的代码,跑出最终的 DOM 结果,返回给用户。

这样既加快了速度,又减少了流量的开销。

不知你有没有发现,前面讲了前后端分离,这种做法看起来是不是又前后端结合了呢?

其实并没有,SSR 对于后端而言,应该做成一个无感知的框架,相当于为前端提供了一种 Cache 服务。

后端还是后端,前端还是前端,两者并没有发生业务上的耦合。

构建阶段

还有一种更加纯粹的 SSR 方法,其实称之为 BSR(Builder-Side Rendering) 更合适。

具体说来,是在 Webpack 工程构建完成的时候,跑起一个无头浏览器(如 Puppeteer),按照 Router 把每一个网页都跑一遍,对每个网页,都将浏览器执行的最终结果保存为静态 HTML 页面。

这样,一个 React 应用就完全变成了跟 Hexo、Jekyll 一样的静态博客了。

react-static 就是基于这种方法进行的。许多流行的 React Static Site Generator 都是基于这种方法进行的(如 Gatsby)。

react-static 介绍

说了一大堆,终于说到我们系列的主角——react-static

它是一个渐进式的 React 静态网站生成器。怎么理解呢?

React SPA 静态网站生成器,这个我们已经在前几节中讲明白了。

至于渐进式是什么意思,我们先不管它。

有了 react-static,不是说任何 React 网站通过它都能变静态。react-static 是一个框架,它定义了一些规则。React 应用必须按照他定义的概念、规则来编写,才能工作。

如果是一个既有的 React 应用,则必须按照 react-static 的概念、规则进行改造,才能工作。

react-static 所定义的概念、规则,我打算放在下一篇中详细介绍。

在本文中,我们只要理解为:

  • 这些规则定义了如何提供数据,如何将数据关联到对应的 React 组件,如何定义模板
  • 而 react-static 是一个命令,它会加载数据、绑定模板、Webpack 构建应用
  • 最后调用 Puppeteer 进行统一拍照,生成纯静态网站。

One more thing

如果认为 SPA 静态化打出了纯静态 HTML 站就完了,那就太天真了。

在每个纯静态 HTML 的末尾,依旧会有对 Bundle 的引用链接!

这是什么意思呢?

  • 还记得经过 SSR 渲染后的网页缩减到几 KB 吗?
  • 通过快速加载,它会立刻展示到用户的屏幕上
  • 之后浏览器会默默地下载 Bundle
  • 下载好后会默默地执行 React、我们的业务框架
  • 直到把这个页面重新再跑出来(与 SSR 渲染是一致的)
  • 由于 React 的 Virtual DOM 机制,网页没有任何的更新

因此,这一切的发生在背后,而用户毫无感知。

那这有什么用呢?这样做的用处是:

  • 用户看到的第一个网页是纯静态的,加载速度飞快。
  • 在不知不觉中,由静态页升级为交互丰富的 React SPA
  • 用户在后续的操作中将体验到这些交互
    • 比如微博提醒
    • 消息推送
    • ……

费了这么大的劲,就是让用户用访问静态网站的速度,得到 SPA 的丰富体验。

在 maxiee.github.io 上有一个今年过了多少天的进度条。如果你仔细看会发现:

  • 首次打开 url 时进度条的进度是过时的
  • 过了一会儿它会更新到正确的值

知道这是为什么了吧!那个过时的时间正是我构建(Snapshot)的时间,哈哈。

结论

没想到写了这么多,这个概念想说清楚的确不容易。

由于我不是专门的前端工程师,在术语和理解上会有错误和理解不对的地方。欢迎批评指正,可以通过微博或者邮件与我联系。

我会在学习理解正确后改正错误,多谢!

react-static 是一个很好的框架,通过它很多网站都能静态化,这会节省很多服务器开销。

像我就直接关闭了之前的 Wordpress 虚拟主机,改在 GitHub Pages 上托管博客。

免费省钱到不是我刻意追求的,主要是省时省力,好维护,折腾起来也更有意思。

在后续的文章中,我将会介绍 react-static 的各种使用方法。欢迎关注!