大多为静态的网站

为内容和营销网站提供接近零的 JavaScript,同时保留交互岛。

默认情况下,Nuxt 会先对页面进行服务端渲染,然后再对整个页面进行 hydration。对于一个 95% 都是静态文本和图片的页面来说,这些 hydration JavaScript 基本上是额外开销:它会延迟交互,并与内容争夺带宽和 CPU,从而可能损害诸如移动端 PageSpeed 分数之类的指标。

Nuxt 提供了避免在不需要的地方付出这部分成本的组件。这个方案将它们结合起来:

  1. 预渲染 你的内容路由,在构建时完成。
  2. 使用 noScripts 路由规则,移除这些路由上的脚本。
  3. 对于需要服务端渲染但不需要交互性的组件,使用 服务器组件
  4. 在保留脚本的路由上使用 延迟 hydration,这样只有在需要时才为交互付费。

预渲染并移除脚本

对于完全没有客户端交互的路由,将 routeRules 中的 prerendernoScripts 结合使用:

nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    '/blog/**': { prerender: true, noScripts: true },
    '/about': { prerender: true, noScripts: true },
  },
})

这些路由会在构建时渲染为纯 HTML,渲染后的页面会省略 Nuxt 入口脚本、导入映射、payload 脚本以及 JS 资源提示。CSS 仍然会包含在内。其结果是页面几乎没有 JavaScript 成本。

进一步了解 noScripts 会精确省略哪些内容,以及全局的 features.noScripts 选项。
noScripts 页面不会进行 hydration。页面上的任何内容都不可交互:没有 @click 处理器,没有 <NuxtLink> 预取(链接仍然会像普通 <a> 标签一样正常工作),也不能从该页面进行客户端导航。仅在你确实需要这种行为的路由上使用它。

用于服务端渲染小部件的岛屿

静态页面的某些部分仍然适合做成带有真实逻辑的组件:渲染后的 markdown、语法高亮代码、CMS 内容。为此请使用 服务端组件。它们在服务端渲染,其依赖项不会进入客户端 bundle:

Directory Structure
-| app/
---| components/
-----| HighlightedMarkdown.server.vue
app/pages/blog/[slug].vue
<template>
  <article>
    <h1>{{ post.title }}</h1>
    <HighlightedMarkdown :markdown="post.body" />
  </article>
</template>

像这样的静态岛屿在 noScripts 路由上也能正常工作,因为它们的 HTML 会在渲染时嵌入。

岛屿内部的完全交互式小部件(标记为 nuxt-client 的组件,或交互式岛屿插槽)在 noScripts 路由上无法工作。岛屿传送内容会在水合前通过一小段内联脚本重新定位到正确位置;在禁用脚本时,这种重新定位无法执行,因而不会发生水合。如果某个页面哪怕只需要一个交互式小部件,也应在该路由上保持脚本开启,并改用延迟水合。

其余部分的延迟 Hydration

对于需要一些交互性的路由,保留脚本启用,但使用延迟 hydration来控制组件何时进行 hydration:

app/pages/index.vue
<template>
  <div>
    <HeroSection />
    <!-- 永远不需要变为可交互的静态内容 -->
    <LazyTestimonials hydrate-never />
    <!-- 仅在滚动到视图中时进行 hydration -->
    <LazyNewsletterSignup hydrate-on-visible />
    <!-- 在浏览器空闲时进行 hydration -->
    <LazyCookieBanner hydrate-on-idle />
  </div>
</template>

hydrate-never 在内容型网站上特别有用:组件的 HTML 会在服务端渲染,但它永远不会被 hydration,因此它的 JavaScript 也永远不会执行(不过属性变化仍然会触发 hydration)。

整合起来

一个典型的以静态内容为主的网站最终会是这样的:

nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    // 纯内容:预渲染,零 JS
    '/': { prerender: true, noScripts: true },
    '/blog/**': { prerender: true, noScripts: true },
    // 带有交互式组件的页面:预渲染,但保留脚本
    '/contact': { prerender: true },
  },
})
  • 内容路由只会输出纯 HTML 和 CSS
  • islands(.server.vue 组件)会在各处处理服务端渲染的组件,同时将沉重依赖排除在客户端包之外
  • 少数需要交互的路由会保留脚本,并通过 hydrate-on-visible / hydrate-on-idle / hydrate-never 来限制浏览器前期要做的工作量
进一步了解预渲染,包括选择性预渲染和 payload 提取。
进一步了解 Nuxt 应用的一般性能最佳实践。