文章·  

Nuxt 在边缘

了解我们如何让 Nuxt 3 能够在边缘运行时运行,从而在靠近用户的地方进行服务器端渲染。
Sébastien Chopin

Sébastien Chopin

@atinux

介绍

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

Worker 的运行时与 Node.js 或浏览器不同,它使用 V8(Google Chrome 开发的 JavaScript 引擎)执行代码。到目前为止,您可以在他们的平台上运行的是在到达服务器之前在边缘运行的小脚本,以提高性能或根据请求头添加一些逻辑,例如。

2020年11月,在开发 Nuxt 3 时,我们大胆尝试在生产环境中将 Nuxt 运行在边缘运行时 / V8 隔离环境上

这使得在使用 Cloudflare Workers 等平台时,能够从世界各地在约50毫秒内服务器渲染页面,而无需处理服务器、负载均衡器和缓存,费用约为每百万请求0.3美元。截至今天,新的平台正在出现,如 Deno Deploy,允许在 V8 隔离环境上运行应用程序。

2024年更新:我发布了NuxtHub,让您可以在 Cloudflare 账户上零配置地使用 Nuxt 构建全栈边缘应用程序。它包括数据库、Blob 存储、KV、远程存储等。

挑战

为了让 Nuxt 在 Worker 中运行,我们不得不重写 Nuxt 的一些部分,使其与环境无关(可在 Node.js、浏览器或 V8 中运行)。

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

在 Nuxt 2 中,在内存中启动生产服务器(也称为冷启动)的持续时间约为300毫秒,因为我们必须加载服务器和应用程序的所有依赖项才能处理请求。

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

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

为了在边缘运行 Nuxt,我们面临另一个挑战:生产打包大小。这包括服务器、Vue 应用程序和 Node.js 依赖项的组合。Cloudflare Workers 目前对 Worker 大小有1MB(免费计划)和5MB(每月5美元计划)的限制。

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

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

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

这就是为什么您可以在

上部署具有边缘渲染的 Nuxt 应用程序。我们还支持许多其他部署提供商,包括 静态托管传统的 Node.js 无服务器和服务器托管

推动全栈能力

现在 Nuxt 可以在边缘运行时运行,我们可以做的不仅仅是渲染一个 Vue 应用程序。多亏了 server 目录,创建一个 API 路由只需要一个 TypeScript 文件。

要添加 /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)。

此外,通过利用对象关系映射 (ORM) 如drizzle-orm,开发人员可以连接边缘和无服务器数据库,例如D1, Turso, Neon, Planetscale等等。

我创建了Atidone,一个开源演示,展示了一个具有身份验证和数据库的全栈应用程序在边缘运行。源代码可在 GitHub 上获得,采用 MIT 许可证:atinux/atidone.

结论

我们对边缘渲染及其所带来的可能性感到兴奋。Nuxt 团队迫不及待地想看到您在此基础上构建什么!

欢迎加入我们的Discord 服务器或在 Twitter 上提及@nuxt_js分享您的作品。