Article·  

Nuxt on the Edge

了解我们如何使 Nuxt 3 能够在边缘运行时上运行,并在靠近用户的位置进行服务器端渲染。

引言

2017 年 9 月,Cloudflare 推出了 Cloudflare Workers,使得我们可以在他们的边缘网络上运行 JavaScript。这意味着你的代码将在全球一百多个位置的整个边缘网络上部署,大约在 30 秒内完成。利用该技术,你可以专注于编写靠近用户所在位置的应用程序,无论他们在世界的哪个地方(延迟约为 50 毫秒)。

与 Node.js 或浏览器的运行时不同,Worker 的运行时是基于 V8 的,V8 是由 Google Chrome 开发的 JavaScript 引擎。在此之前,你只能在他们的平台上运行一些小型脚本,这些脚本会在到达服务器之前在边缘上运行,以提高性能或基于请求头添加一些逻辑。

2020 年 11 月,在开发 Nuxt 3 期间,我们决定将 Nuxt 生产环境下运行在边缘运行时 / V8 隔离环境

这使得在使用类似于 Cloudflare Workers 的平台时,你可以在全球范围内以约 50 毫秒的速度进行服务器端渲染,而无需担心服务器、负载均衡和缓存问题,其费用为约每百万次请求 0.3 美元(费用详情)。时至今日,新的平台也开始出现,允许在 V8 隔离环境中运行应用程序,例如 Deno Deploy。

2024 更新: 我发布了 NuxtHub,你可以在其中使用 Nuxt 在边缘上构建全栈应用,无需进行任何配置,直接使用你的 Cloudflare 帐户进行部署。它包括数据库、二进制大对象存储、KV 存储、远程存储以及更多功能。

挑战

为了让 Nuxt 在 Worker 上运行,我们不得不重写 Nuxt 的某些部分,使其能够适应各种环境(Node.js、浏览器或 V8)。

我们从服务器开始,并创造了 unjs/h3:一个专为高性能和可移植性而构建的最小化 HTTP 框架。它取代了我们在 Nuxt 2 中使用的 Connect,但同时也与 Connect 保持兼容,因此你可以继续使用 Connect/Express 中间件。在 Worker 中,对于每个传入的请求,它会启动 Nuxt 的生产环境,将请求发送给它,并将响应返回。

在 Nuxt 2 中,启动服务器的时间(也称为冷启动)约为 ~300 毫秒,这是因为我们需要加载服务器和应用程序的所有依赖项以处理请求。

通过处理 h3,我们决定将附加到服务器的每个处理程序进行代码拆分,并在请求时仅在需要时进行延迟加载。当你启动 Nuxt 3 时,我们只会将 h3 和对应的处理程序加载到内存中。当请求到达时,我们加载与路由对应的处理程序并执行它。

通过采用这种方法,我们将冷启动时间从约 300 毫秒减少到约 2 毫秒

我们在将 Nuxt 部署在边缘运行时上面临另一个挑战:生成的捆绑文件大小。这包括服务器、Vue 应用程序和 Node.js 依赖项的组合。目前,Cloudflare Workers 在 worker 大小方面有一个限制,免费计划为 1MB,每月 5 美元的计划为 5MB。

为了实现这一点,我们创建了 unjs/nitro,这是我们的服务器引擎。当运行 nuxt build 命令时,它会将你的整个项目打包,并将所有依赖项包括在最终输出中。它使用 Rollupvercel/nft 来跟踪 node_modules 中使用的代码,以删除不必要的代码。基本的 Nuxt 3 应用程序生成的输出的总大小约为 700kB(经过 gzip 压缩)

最后,为了在开发(Node.js)和在 Cloudflare 上的生产环境(边缘运行时)之间提供相同的开发者体验,我们创建了 unjs/unenv:一个库,用于转换 JavaScript 代码以在任何地方(平台无关)运行,通过模拟或添加已知依赖项的 polyfill。

在 Nuxt,我们相信你应该拥有选择适合你的最佳托管提供商的自由。

这就是为什么你可以通过边缘端渲染来部署 Nuxt 应用程序的原因:

我们还支持许多其他部署提供商,包括静态托管传统的 Node.js 无服务器和服务器托管商

推动全栈能力

现在我们在边缘运行时上运行 Nuxt,我们可以做更多事情,不仅仅是渲染 Vue 应用程序。借助服务器目录,只需一个 TypeScript 文件,你就可以创建一个 API 路由。

要添加 /api/hello 路由,创建一个名为 server/api/hello.ts 的文件:

server/api/hello.ts
export default defineEventHandler((event) => {
  return {
    hello: 'world'
  }
})

现在你可以在页面和组件中通用地调用此 API:

pages/index.vue
<script setup>
const { data } = await useFetch('/api/hello')
</script>

<template>
  <pre>{{ data }}</pre>
</template>

需要注意的一点是,当我们创建 useFetch$fetch 时,在服务器端渲染期间,如果调用 API 路由,它将模拟请求并直接调用函数代码:避免了 HTTP 请求,减少了页面的渲染时间

在开发体验方面,你会注意到当创建服务器文件时,Nuxt 服务器会继续运行,而不需要重新构建 Vue 应用程序。这是因为 Nuxt 3 支持创建 API 和服务器路由时的热模块替换(HMR)

此外,通过利用类似于 drizzle-orm 的对象关系映射(ORM),开发者可以连接边缘和无服务器数据库,如 D1TursoNeonPlanetscale 等等。

我创建了 Atidone,这是一个开源示例,展示了在边缘上运行具有身份验证和数据库的全栈应用程序。源代码在 GitHub 上以 MIT 许可证的形式提供,地址为 atinux/atidone

结论

我们对边缘端渲染及其带来的潜力感到兴奋。我们 Nuxt 团队迫不及待地想看看你们将在此基础上构建的作品!

欢迎加入我们的 Discord 服务器 或在 Twitter 上 @nuxt_js 提及我们,与我们分享你的工作。