关于 nuxt generate 和 prerender-spa-plugin 的比较

关于 nuxt generate 和 prerender-spa-plugin 的比较

2020-06-28
上海

TL; DR

我放弃了 nuxt generate 转而使用 prerender-spa-plugin,因为后者才是真正的预渲染。

想要单页应用的体验,又想要搜索引擎友好,还想省点计算资源,那就搞个预渲染⑧。

传闻基于 vue.jsnuxt.js 无论是做服务端渲染还是预渲染都特别方便,所以我有点感兴趣——一句 nuxt generate 就能完成预渲染,懒人必备。

以本网站的 repository 为例子,我们康康 nuxt 预渲染的效果。

首先是渲染后的 dist 目录的结构,大概是这样:

│  index.html
│
├─about
│      index.html
│
├─article
│  │  index.html
│  │
│  ├─0
│  │      index.html
│  │
│  └─1
│          index.html

看着还行,然后打开网站首页对应的 html 文件(省略了 style 标签),出大问题:

<!doctype html>
<html>

<head>
  <title>solidays</title>
  <meta data-n-head="1" charset="utf-8">
  <meta data-n-head="1" name="viewport" content="width=device-width,initial-scale=1">
  <meta data-n-head="1" data-hid="description" name="description" content="Solidays site">
  <link data-n-head="1" rel="icon" type="image/x-icon" href="/favicon.ico">
  <link rel="preload" href="/_nuxt/87c5ca9e89ccc22c5ca6.js" as="script">
  <link rel="preload" href="/_nuxt/617d5b8dda09eb86232c.js" as="script">
  <link rel="preload" href="/_nuxt/b67b01560b96c9cde957.js" as="script">
</head>

<body>
  <div id="__nuxt">
    <script>window.addEventListener("error", function () { var e = document.getElementById("nuxt-loading"); e && (e.className += " error") })</script>
    <div id="nuxt-loading" aria-live="polite" role="status">
      <div>Loading...</div>
    </div>
  </div>
  <script type="text/javascript" src="/_nuxt/87c5ca9e89ccc22c5ca6.js"></script>
  <script type="text/javascript" src="/_nuxt/617d5b8dda09eb86232c.js"></script>
  <script type="text/javascript" src="/_nuxt/b67b01560b96c9cde957.js"></script>
</body>

</html>

所以说预渲染就渲染出了个 Loading... 而已,首页里并没有涉及到异步数据,却啥也没渲染出来,实在很一般。

结论就是 nuxt generate 是可以生成一个静态站点的,生成的站点不需要像传统的单页应用一样配置 fallback 的页面(因为它已经把所有路由情况都用相应的目录表示出来了),所以对托管的服务器没啥要求。但我觉得这个预渲染的程度也太低了(相应的,也许速度会很快),当然,也可能是我食用方法不对。

另一个可用于 vue.js 预渲染的技术是 prerender-spa-plugin,作者是 vue 核心团队的成员。

prerender-spa-plugin 预渲染出来目录结构跟 nuxt 的是一样的:

│  index.html
│
├─about
│      index.html
│
├─article
│  │  index.html
│  │
│  ├─0
│  │      index.html
│  │
│  └─1
│          index.html

但是打开首页对应的 html 文件,正常多了,页面上的元素都被预渲染了出来。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link rel="icon" href="/favicon.ico">
    <title>solidays</title>
    <link href="/css/about.0f584ee5.css" rel="prefetch">
    <link href="/css/chunk-28e366f6.b9ab4f5a.css" rel="prefetch">
    <link href="/css/chunk-6941575a.2572d488.css" rel="prefetch">
    <link href="/js/about.6337d6a7.js" rel="prefetch">
    <link href="/js/chunk-28e366f6.773b388b.js" rel="prefetch">
    <link href="/js/chunk-6941575a.441841bb.js" rel="prefetch">
    <link href="/css/app.9449ebb6.css" rel="preload" as="style">
    <link href="/js/app.7fed66b8.js" rel="preload" as="script">
    <link href="/js/chunk-vendors.3dec5244.js" rel="preload" as="script">
    <link href="/css/app.9449ebb6.css" rel="stylesheet">
</head>

<body><noscript><strong>We're sorry but solidays doesn't work properly without JavaScript enabled. Please enable it to
            continue.</strong></noscript>
    <div id="app">
        <div data-v-18813c80="" class="root">
            <div data-v-18813c80="" class="container">
                <h1 data-v-18813c80="" class="title"> solidays </h1>
                <div data-v-18813c80="" class="categories"><a data-v-18813c80="" href="/article" class=""> 文章 </a><a
                        data-v-18813c80="" href="/about" class=""> 关于 </a><a data-v-18813c80=""
                        href="https://www.solidays.tk"> 旧站 </a></div>
            </div>
            <div data-v-18813c80="" class="footer"><a data-v-18813c80="" href="https://世界1流大学.com">世界1流大学.com</a><a
                    data-v-18813c80="" href="https://github.com/ReGetALife/solidays">源码仓库</a></div>
        </div>
    </div>
    <script src="/js/chunk-vendors.3dec5244.js"></script>
    <script src="/js/app.7fed66b8.js"></script>
</body>

</html>

但是打开某篇文章对应的 html 文件,发现元素并没有被渲染出来,因为文章都是异步请求获取的,文章还没拿到已经完成了渲染,自然不会有内容。所以只需设置适当的延时即可:

new PrerenderSpaPlugin({
          staticDir: path.join(__dirname, 'dist'),
          // prerender routes
          routes,
          renderer: new Renderer({
            // time to fetch md file
            renderAfterTime: 500,
            headless: true
          })
        })

于是,所有页面的所有元素都被渲染了出来,很棒棒。

prerender-spa-plugin 提供了两个渲染器:puppeteer 和 jsdom ,前者是一个无头 chrome 浏览器,比较可靠但耗内存,后者很快但不可靠。所以使用 puppeteer 时,就像打开了心爱的 chrome 渲染完了页面再保存下来,这才叫预渲染啊。

Metadata 和 SEO 啥的暂时放下,最近挺忙的 : )

在 GitHub 上查看本页