Nuxt 团队与 Google 的 Chrome Aurora 团队合作,很高兴地宣布公开发布 Nuxt Scripts 的公开 Beta 版本。
Nuxt Scripts 是使用第三方脚本的更佳方式,可提供改进的性能、隐私、安全和开发者体验。
开始使用 Nuxt Scripts
一年多以前,Daniel 发布了最初的 Nuxt Scripts RFC。该 RFC 提议创建一个模块,该模块将“允许管理和优化第三方脚本,遵循高性能和合规网站的最佳实践”。
凭借解决与第三方脚本相关的性能问题的 个人经验,我知道这些性能优化可能有多么困难。尽管如此,我仍然热衷于解决这个问题并接管了这个项目。
以 RFC 作为想法的种子,我开始原型化它可能 看起来的样子,使用了 Unhead。
在思考我究竟想构建什么时,我发现真正的问题不仅仅是如何加载“优化过的”第三方脚本,而是如何使使用第三方脚本的整体体验更好。
为何构建第三方脚本模块?
94% 的网站至少使用一个第三方提供商,平均每个网站有 五个第三方提供商。
我们知道第三方脚本并不完美;它们拖慢了网页速度,导致隐私和安全问题,并且难以使用。
然而,它们从根本上是有用的,并且在短期内不会消失。
通过探索第三方脚本的问题,我们可以看到可以在哪些方面进行改进。
😒 开发者体验:全栈难题
让我们逐步了解如何使用一个虚构的 tracker.js
脚本将第三方脚本添加到您的 Nuxt 应用程序中,该脚本向 window 添加了一个 track
函数。
我们首先使用 useHead
加载脚本。
useHead({ scripts: [{ src: '/tracker.js', defer: true }] })
但是,现在让我们尝试在我们的应用程序中使脚本功能正常工作。
以下是在 Nuxt 中使用第三方脚本时的常见步骤
- 一切都必须为了 SSR 安全性而包裹起来。
- 脚本是否已加载的不可靠检查。
- 为了类型而增强 window 对象。
<script setup>
// ❌ Oops, window is not defined!
// 💡 The window can't be directly accessed if we use SSR in Nuxt.
// 👉 We need to make this SSR safe
window.track('page_view', useRoute().path)
</script>
🐌 性能:“为什么我的 Lighthouse 得不到 100 分?”
为了让访问者开始与您的 Nuxt 网站互动,需要下载应用程序包,并且 Vue 需要水合应用程序实例。
即使在使用 async
或 defer
时,加载第三方脚本也可能会干扰此水合过程。这会减慢网络速度并阻塞主线程,从而导致用户体验下降和较差的 Core Web Vitals。
Chrome 用户体验报告显示,具有大量第三方资源的 Nuxt 网站通常具有较差的 交互到下次绘制 (INP) 和 最大内容渲染 (LCP) 分数。
为了了解第三方脚本如何降低性能,我们可以查看 Web Almanac 2022。《报告》显示,前 10 大第三方脚本的平均中位数阻塞时间为 1.4 秒。
🛡️ 隐私与安全:不做恶事?
在前 10,000 个网站中,有 58% 的网站的第三方脚本会交换存储在外部 Cookie 中的跟踪 ID,这意味着即使禁用了第三方 Cookie,它们也可以跨网站跟踪用户。
虽然在许多情况下,我们对使用的提供商无能为力,但我们应尽可能尝试最大限度地减少泄露最终用户的数据量。
当我们确实考虑隐私影响时,可能很难在我们的隐私政策中准确传达这些信息,并构建符合 GDPR 等法规所需的同意管理。
使用第三方脚本时的安全性也是一个问题。第三方脚本是恶意行为者的常见攻击媒介,大多数脚本不提供其脚本的 integrity
哈希值,这意味着它们可能随时被入侵并将恶意代码注入到您的应用程序中。
Nuxt Scripts 如何解决这些问题?
组合式函数:useScript
此组合式函数位于 <script>
标签和添加到 window.{thirdPartyKey}
的功能之间。
对于 <script>
标签,此组合式函数
- 完全可见脚本的加载和错误状态
- 默认情况下,在 Nuxt 水合应用程序时加载脚本,以获得稍微更好的性能。
- 限制
crossorigin
和referrerpolicy
以提高隐私和安全性。 - 提供一种延迟加载脚本直到您需要它的方法。
对于脚本 API,它
- 围绕脚本函数提供完全的类型安全
- 添加代理层,允许您的应用程序在不安全的环境(SSR、脚本加载之前、脚本被阻止)中运行脚本函数
const { proxy, onLoaded } = useScript('/hello.js', {
trigger: 'onNuxtReady',
use() {
return window.helloWorld
}
})
onLoaded(({ greeting }) => {
// ✅ script is loaded! Hooks into Vue lifecycle
})
// ✅ OR use the proxy API - SSR friendly, called when script is loaded
proxy.greeting() // Hello, World!
declare global {
interface Window {
helloWorld: {
greeting: () => 'Hello World!'
}
}
}
脚本注册表
脚本注册表是常见第三方脚本的第一方集成的集合。截至发布时,我们支持 21 个脚本,未来还将推出更多。
这些注册表脚本是围绕 useScript
进行微调的封装器,具有完整的类型安全性、脚本选项的运行时验证(仅限开发环境)和环境变量支持
例如,我们可以查看 Fathom Analytics 脚本。
const { proxy } = useScriptFathomAnalytics({
// ✅ options are validated at runtime
site: undefined
})
// ✅ typed
proxy.trackPageview()
外观组件
注册表包含几个外观组件,例如 Google 地图、YouTube 播放器 和 Intercom。
外观组件是“假的”组件,在第三方脚本加载时会被水合。外观组件有其权衡之处,但可以极大地提高您的性能。有关更多信息,请参阅什么是外观组件?指南。
Nuxt Scripts 提供外观组件作为可访问但无头组件,这意味着它们默认情况下没有样式,但添加了必要的 a16y 数据。

点击加载
同意管理和元素事件触发器
useScript
组合式函数使您可以完全控制脚本的加载方式和时间,方法是提供自定义的 trigger
或手动调用 load()
函数。
在此基础上,Nuxt Scripts 提供了更高级的触发器,使其更加容易。
const cookieConsentTrigger = useScriptTriggerConsent()
const { proxy } = useScript<{ greeting: () => void }>('/hello.js', {
// script will only be loaded once the consent has been accepted
trigger: cookieConsentTrigger
})
// ...
function acceptCookies() {
cookieConsentTrigger.accept()
}
// greeting() is queued until the user accepts cookies
proxy.greeting()
捆绑脚本
在许多情况下,我们从不受我们控制的域加载第三方脚本。这可能会导致许多问题
- 隐私:第三方脚本可以跨网站跟踪用户。
- 安全:第三方脚本可能被入侵并注入恶意代码。
- 性能:额外的 DNS 查询将减慢页面加载速度。
- 开发者体验:已同意的脚本可能被广告拦截器阻止。
为了缓解这种情况,Nuxt Scripts 提供了一种将第三方脚本捆绑到您的 public 目录中的方法,而无需任何额外的工作。
useScript('https://cdn.jsdelivr.net.cn/npm/js-confetti@latest/dist/js-confetti.browser.js', {
bundle: true,
})
现在,该脚本将从您自己域名的 /_scripts/{hash}
提供。
未完待续
正如我们所见,有很多机会可以改进第三方脚本,从而为开发者和最终用户带来更好的体验。
Nuxt Scripts 的初始版本已经解决了一些问题,但我们仍然有很多工作要做。
路线图上的下一个项目是
我们欢迎您的贡献和支持。
开始使用
为了开始使用 Nuxt Scripts,我们创建了一个教程,以帮助您快速入门和运行。
鸣谢
- Harlan Wilton - Nuxt (作者)
- Julien Huang - Nuxt (贡献者)
- Daniel Roe - Nuxt (贡献者)
- Chrome Aurora - Google (贡献者)
并衷心感谢早期的贡献者。